From 503988132d2f74ad428a8fa10dbda9a8bc22f394 Mon Sep 17 00:00:00 2001 From: M Hightower <27247790+mhightower83@users.noreply.github.com> Date: Fri, 17 Apr 2020 14:19:46 -0700 Subject: [PATCH 01/29] fNull pointer call from WiFiClient::localIP() to IPAddress (#7221) Fixes exception 28 in IPAddress(const ipv4_addr* fw_addr); with null ip_addr pointer passed in by WiFiCient.cpp localIP(). I assumed that localIP() was called shortly after _pcb became null. --- libraries/ESP8266WiFi/src/WiFiClient.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/ESP8266WiFi/src/WiFiClient.cpp b/libraries/ESP8266WiFi/src/WiFiClient.cpp index 059c6c663..abd277229 100644 --- a/libraries/ESP8266WiFi/src/WiFiClient.cpp +++ b/libraries/ESP8266WiFi/src/WiFiClient.cpp @@ -367,7 +367,7 @@ uint16_t WiFiClient::remotePort() IPAddress WiFiClient::localIP() { - if (!_client) + if (!_client || !_client->getLocalAddress()) return IPAddress(0U); return IPAddress(_client->getLocalAddress()); @@ -389,7 +389,7 @@ void WiFiClient::stopAll() } -void WiFiClient::stopAllExcept(WiFiClient* except) +void WiFiClient::stopAllExcept(WiFiClient* except) { for (WiFiClient* it = _s_first; it; it = it->_next) { if (it != except) { From a36a6c8a3f4f6c20db4cefce9616d93af812d736 Mon Sep 17 00:00:00 2001 From: Viktr Date: Sat, 18 Apr 2020 01:42:07 +0300 Subject: [PATCH 02/29] Add isRunning getter to check is ::begin already called (#7219) --- libraries/ESP8266mDNS/src/LEAmDNS.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS.h b/libraries/ESP8266mDNS/src/LEAmDNS.h index 39b2bad81..fe02560ae 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS.h +++ b/libraries/ESP8266mDNS/src/LEAmDNS.h @@ -194,6 +194,11 @@ public: // for compatibility... bool setHostname(const String& p_strHostname); + bool isRunning(void) + { + return (m_pUDPContext != 0); + } + /** hMDNSService (opaque handle to access the service) */ From 77b82a0c27eded5b9e1eabe2ffa39daa0cd12e29 Mon Sep 17 00:00:00 2001 From: Viktr Date: Sat, 18 Apr 2020 02:31:31 +0300 Subject: [PATCH 03/29] Changing listen to listen the current iface only instead of 0 (#7217) --- libraries/ESP8266mDNS/src/LEAmDNS_Helpers.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS_Helpers.cpp b/libraries/ESP8266mDNS/src/LEAmDNS_Helpers.cpp index d23941ce5..e6d39914f 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS_Helpers.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS_Helpers.cpp @@ -226,7 +226,7 @@ bool MDNSResponder::_allocUDPContext(void) m_pUDPContext = new UdpContext; m_pUDPContext->ref(); - if (m_pUDPContext->listen(IP4_ADDR_ANY, DNS_MQUERY_PORT)) + if (m_pUDPContext->listen(&m_netif->ip_addr, DNS_MQUERY_PORT)) { m_pUDPContext->setMulticastTTL(MDNS_MULTICAST_TTL); m_pUDPContext->onRx(std::bind(&MDNSResponder::_callProcess, this)); From ce0e63f62938a7cf850447edceb9db073ec5787d Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sat, 18 Apr 2020 03:37:12 +0200 Subject: [PATCH 04/29] fix style checking: (#7222) - (some) core files are astyled - ci was only checking libraries => now everything is checked --- cores/esp8266/core_esp8266_si2c.cpp | 76 ++++++++++++++++------------- tests/ci/style_check.sh | 2 +- 2 files changed, 43 insertions(+), 35 deletions(-) diff --git a/cores/esp8266/core_esp8266_si2c.cpp b/cores/esp8266/core_esp8266_si2c.cpp index dff91ce0f..3dfc5d1c7 100644 --- a/cores/esp8266/core_esp8266_si2c.cpp +++ b/cores/esp8266/core_esp8266_si2c.cpp @@ -31,31 +31,31 @@ extern "C" { #include "ets_sys.h" }; - // Inline helpers - static inline __attribute__((always_inline)) void SDA_LOW(const int twi_sda) - { - GPES = (1 << twi_sda); - } - static inline __attribute__((always_inline)) void SDA_HIGH(const int twi_sda) - { - GPEC = (1 << twi_sda); - } - static inline __attribute__((always_inline)) bool SDA_READ(const int twi_sda) - { - return (GPI & (1 << twi_sda)) != 0; - } - static inline __attribute__((always_inline)) void SCL_LOW(const int twi_scl) - { - GPES = (1 << twi_scl); - } - static inline __attribute__((always_inline)) void SCL_HIGH(const int twi_scl) - { - GPEC = (1 << twi_scl); - } - static inline __attribute__((always_inline)) bool SCL_READ(const int twi_scl) - { - return (GPI & (1 << twi_scl)) != 0; - } +// Inline helpers +static inline __attribute__((always_inline)) void SDA_LOW(const int twi_sda) +{ + GPES = (1 << twi_sda); +} +static inline __attribute__((always_inline)) void SDA_HIGH(const int twi_sda) +{ + GPEC = (1 << twi_sda); +} +static inline __attribute__((always_inline)) bool SDA_READ(const int twi_sda) +{ + return (GPI & (1 << twi_sda)) != 0; +} +static inline __attribute__((always_inline)) void SCL_LOW(const int twi_scl) +{ + GPES = (1 << twi_scl); +} +static inline __attribute__((always_inline)) void SCL_HIGH(const int twi_scl) +{ + GPEC = (1 << twi_scl); +} +static inline __attribute__((always_inline)) bool SCL_READ(const int twi_scl) +{ + return (GPI & (1 << twi_scl)) != 0; +} // Implement as a class to reduce code size by allowing access to many global variables with a single base pointer @@ -126,10 +126,12 @@ private: { esp8266::polledTimeout::oneShotFastUs timeout(twi_clockStretchLimit); esp8266::polledTimeout::periodicFastUs yieldTimeout(5000); - while(!timeout && !SCL_READ(twi_scl)) // outer loop is stretch duration up to stretch limit - { + while (!timeout && !SCL_READ(twi_scl)) // outer loop is stretch duration up to stretch limit + { if (yieldTimeout) // inner loop yields every 5ms + { yield(); + } } } @@ -161,23 +163,29 @@ static Twi twi; void Twi::setClock(unsigned int freq) { if (freq < 1000) // minimum freq 1000Hz to minimize slave timeouts and WDT resets + { freq = 1000; - + } + preferred_si2c_clock = freq; #if F_CPU == FCPU80 if (freq > 400000) + { freq = 400000; + } twi_dcount = (500000000 / freq); // half-cycle period in ns - twi_dcount = (1000*(twi_dcount - 1120)) / 62500; // (half cycle - overhead) / busywait loop time - + twi_dcount = (1000 * (twi_dcount - 1120)) / 62500; // (half cycle - overhead) / busywait loop time + #else if (freq > 800000) + { freq = 800000; + } twi_dcount = (500000000 / freq); // half-cycle period in ns - twi_dcount = (1000*(twi_dcount - 560)) / 31250; // (half cycle - overhead) / busywait loop time + twi_dcount = (1000 * (twi_dcount - 560)) / 31250; // (half cycle - overhead) / busywait loop time #endif } @@ -221,7 +229,7 @@ void Twi::enableSlave() } } - void ICACHE_RAM_ATTR Twi::busywait(unsigned int v) +void ICACHE_RAM_ATTR Twi::busywait(unsigned int v) { unsigned int i; for (i = 0; i < v; i++) // loop time is 5 machine cycles: 31.25ns @ 160MHz, 62.5ns @ 80MHz @@ -463,7 +471,7 @@ void Twi::attachSlaveTxEvent(void (*function)(void)) twi_onSlaveTransmit = function; } -// DO NOT INLINE, inlining reply() in combination with compiler optimizations causes function breakup into +// DO NOT INLINE, inlining reply() in combination with compiler optimizations causes function breakup into // parts and the ICACHE_RAM_ATTR isn't propagated correctly to all parts, which of course causes crashes. // TODO: test with gcc 9.x and if it still fails, disable optimization with -fdisable-ipa-fnsplit void ICACHE_RAM_ATTR Twi::reply(uint8_t ack) @@ -660,7 +668,7 @@ void ICACHE_RAM_ATTR Twi::onSclChange(void) unsigned int scl; // Store bool return in int to reduce final code size. - + sda = SDA_READ(twi.twi_sda); scl = SCL_READ(twi.twi_scl); diff --git a/tests/ci/style_check.sh b/tests/ci/style_check.sh index 5cba58f9f..609b7e457 100755 --- a/tests/ci/style_check.sh +++ b/tests/ci/style_check.sh @@ -12,4 +12,4 @@ ${org}/../restyle.sh git --version || true git submodule foreach --recursive 'git reset --hard' -git diff --exit-code -- $TRAVIS_BUILD_DIR/libraries +git diff --exit-code -- $TRAVIS_BUILD_DIR From 4ca69bc21d61f10510ed5ddd1565a42f3e45a3e4 Mon Sep 17 00:00:00 2001 From: M Hightower <27247790+mhightower83@users.noreply.github.com> Date: Sun, 19 Apr 2020 02:14:20 -0700 Subject: [PATCH 05/29] For example CaptivePortal, update HTML to pass HTML checker. (#7227) Added meta viewport element for better mobile device viewing. For example CaptivePortalAdvanced, increased size of ssid and password array to hold maximums 32 and 64 charcter strings. Added missing HTML elments to main splash and wifi config. They should now pass an HTML checker. Also added meta viewport element for better mobile device viewing. --- .../DNSServer/examples/CaptivePortal/CaptivePortal.ino | 8 +++++--- .../CaptivePortalAdvanced/CaptivePortalAdvanced.ino | 5 ++--- .../examples/CaptivePortalAdvanced/handleHttp.ino | 9 ++++++--- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/libraries/DNSServer/examples/CaptivePortal/CaptivePortal.ino b/libraries/DNSServer/examples/CaptivePortal/CaptivePortal.ino index 53b159502..b59cda2d9 100644 --- a/libraries/DNSServer/examples/CaptivePortal/CaptivePortal.ino +++ b/libraries/DNSServer/examples/CaptivePortal/CaptivePortal.ino @@ -8,9 +8,11 @@ DNSServer dnsServer; ESP8266WebServer webServer(80); String responseHTML = "" - "CaptivePortal" - "

Hello World!

This is a captive portal example. All requests will " - "be redirected here.

"; + "" + "" + "CaptivePortal" + "

Hello World!

This is a captive portal example." + " All requests will be redirected here.

"; void setup() { WiFi.mode(WIFI_AP); diff --git a/libraries/DNSServer/examples/CaptivePortalAdvanced/CaptivePortalAdvanced.ino b/libraries/DNSServer/examples/CaptivePortalAdvanced/CaptivePortalAdvanced.ino index 0f2553fbc..e97dfbc0a 100644 --- a/libraries/DNSServer/examples/CaptivePortalAdvanced/CaptivePortalAdvanced.ino +++ b/libraries/DNSServer/examples/CaptivePortalAdvanced/CaptivePortalAdvanced.ino @@ -30,8 +30,8 @@ const char *softAP_password = APPSK; const char *myHostname = "esp8266"; /* Don't set this wifi credentials. They are configurated at runtime and stored on EEPROM */ -char ssid[32] = ""; -char password[32] = ""; +char ssid[33] = ""; +char password[65] = ""; // DNS server const byte DNS_PORT = 53; @@ -140,4 +140,3 @@ void loop() { //HTTP server.handleClient(); } - diff --git a/libraries/DNSServer/examples/CaptivePortalAdvanced/handleHttp.ino b/libraries/DNSServer/examples/CaptivePortalAdvanced/handleHttp.ino index 1baac437d..dc286c184 100644 --- a/libraries/DNSServer/examples/CaptivePortalAdvanced/handleHttp.ino +++ b/libraries/DNSServer/examples/CaptivePortalAdvanced/handleHttp.ino @@ -9,7 +9,9 @@ void handleRoot() { String Page; Page += F( - "" + "" + "" + "CaptivePortal" "

HELLO WORLD!!

"); if (server.client().localIP() == apIP) { Page += String(F("

You are connected through the soft AP: ")) + softAP_ssid + F("

"); @@ -43,7 +45,9 @@ void handleWifi() { String Page; Page += F( - "" + "" + "" + "CaptivePortal" "

Wifi config

"); if (server.client().localIP() == apIP) { Page += String(F("

You are connected through the soft AP: ")) + softAP_ssid + F("

"); @@ -130,4 +134,3 @@ void handleNotFound() { server.sendHeader("Expires", "-1"); server.send(404, "text/plain", message); } - From ea1fdb210f84fbed421340a1341376e179e4addb Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" <19971886+dok-net@users.noreply.github.com> Date: Sun, 19 Apr 2020 14:57:46 +0200 Subject: [PATCH 06/29] Fixup 7122, new startWaveformCycles more aptly named startWaveformClockCycles (like in rest of core API for this type of use). (#7218) Fix/clarify comments. Fix redundancies in Tone, end Tone waveform on exact period limit for proper sound. Fix redundancies in wiring_pwmExtend Servo to map in-use pins, Tone already has this. --- cores/esp8266/Tone.cpp | 13 ++++++++----- cores/esp8266/core_esp8266_waveform.cpp | 17 +++++++++-------- cores/esp8266/core_esp8266_waveform.h | 19 +++++++++++-------- cores/esp8266/core_esp8266_wiring_pwm.cpp | 7 ++----- libraries/Servo/src/Servo.cpp | 8 +++++++- libraries/Servo/src/Servo.h | 1 + 6 files changed, 38 insertions(+), 27 deletions(-) diff --git a/cores/esp8266/Tone.cpp b/cores/esp8266/Tone.cpp index ff895f153..064fdad5d 100644 --- a/cores/esp8266/Tone.cpp +++ b/cores/esp8266/Tone.cpp @@ -22,14 +22,14 @@ */ #include "Arduino.h" -#include "user_interface.h" #include "core_esp8266_waveform.h" +#include "user_interface.h" // Which pins have a tone running on them? static uint32_t _toneMap = 0; -static void _startTone(uint8_t _pin, uint32_t high, uint32_t low, unsigned long duration) { +static void _startTone(uint8_t _pin, uint32_t high, uint32_t low, uint32_t duration) { if (_pin > 16) { return; } @@ -39,7 +39,10 @@ static void _startTone(uint8_t _pin, uint32_t high, uint32_t low, unsigned long high = std::max(high, (uint32_t)microsecondsToClockCycles(25)); // new 20KHz maximum tone frequency, low = std::max(low, (uint32_t)microsecondsToClockCycles(25)); // (25us high + 25us low period = 20KHz) - if (startWaveformCycles(_pin, high, low, microsecondsToClockCycles(duration * 1000))) { + duration = microsecondsToClockCycles(duration * 1000UL); + duration += high + low - 1; + duration -= duration % (high + low); + if (startWaveformClockCycles(_pin, high, low, duration)) { _toneMap |= 1 << _pin; } } @@ -49,7 +52,7 @@ void tone(uint8_t _pin, unsigned int frequency, unsigned long duration) { if (frequency == 0) { noTone(_pin); } else { - uint32_t period = (1000000L * system_get_cpu_freq()) / frequency; + uint32_t period = microsecondsToClockCycles(1000000UL) / frequency; uint32_t high = period / 2; uint32_t low = period - high; _startTone(_pin, high, low, duration); @@ -63,7 +66,7 @@ void tone(uint8_t _pin, double frequency, unsigned long duration) { if (frequency < 1.0) { // FP means no exact comparisons noTone(_pin); } else { - double period = (1000000.0L * system_get_cpu_freq()) / frequency; + double period = (double)microsecondsToClockCycles(1000000UL) / frequency; uint32_t high = (uint32_t)((period / 2.0) + 0.5); uint32_t low = (uint32_t)(period + 0.5) - high; _startTone(_pin, high, low, duration); diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index e3924cf08..bd9667cf7 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -5,13 +5,13 @@ Copyright (c) 2018 Earle F. Philhower, III. All rights reserved. The core idea is to have a programmable waveform generator with a unique - high and low period (defined in microseconds). TIMER1 is set to 1-shot - mode and is always loaded with the time until the next edge of any live - waveforms. + high and low period (defined in microseconds or CPU clock cycles). TIMER1 is + set to 1-shot mode and is always loaded with the time until the next edge + of any live waveforms. Up to one waveform generator per pin supported. - Each waveform generator is synchronized to the ESP cycle counter, not the + Each waveform generator is synchronized to the ESP clock cycle counter, not the timer. This allows for removing interrupt jitter and delay as the counter always increments once per 80MHz clock. Changes to a waveform are contiguous and only take effect on the next waveform transition, @@ -19,8 +19,9 @@ This replaces older tone(), analogWrite(), and the Servo classes. - Everywhere in the code where "cycles" is used, it means ESP.getCycleTime() - cycles, not TIMER1 cycles (which may be 2 CPU clocks @ 160MHz). + Everywhere in the code where "cycles" is used, it means ESP.getCycleCount() + clock cycle count, or an interval measured in CPU clock cycles, but not TIMER1 + cycles (which may be 2 CPU clock cycles @ 160MHz). This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -112,10 +113,10 @@ void setTimer1Callback(uint32_t (*fn)()) { // waveform smoothly on next low->high transition. For immediate change, stopWaveform() // first, then it will immediately begin. int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS) { - return startWaveformCycles(pin, microsecondsToClockCycles(timeHighUS), microsecondsToClockCycles(timeLowUS), microsecondsToClockCycles(runTimeUS)); + return startWaveformClockCycles(pin, microsecondsToClockCycles(timeHighUS), microsecondsToClockCycles(timeLowUS), microsecondsToClockCycles(runTimeUS)); } -int startWaveformCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles) { +int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles) { if ((pin > 16) || isFlashInterfacePin(pin)) { return false; } diff --git a/cores/esp8266/core_esp8266_waveform.h b/cores/esp8266/core_esp8266_waveform.h index 9a5f2ce37..e42a17f89 100644 --- a/cores/esp8266/core_esp8266_waveform.h +++ b/cores/esp8266/core_esp8266_waveform.h @@ -5,13 +5,13 @@ Copyright (c) 2018 Earle F. Philhower, III. All rights reserved. The core idea is to have a programmable waveform generator with a unique - high and low period (defined in microseconds). TIMER1 is set to 1-shot - mode and is always loaded with the time until the next edge of any live - waveforms. + high and low period (defined in microseconds or CPU clock cycles). TIMER1 is + set to 1-shot mode and is always loaded with the time until the next edge + of any live waveforms. Up to one waveform generator per pin supported. - Each waveform generator is synchronized to the ESP cycle counter, not the + Each waveform generator is synchronized to the ESP clock cycle counter, not the timer. This allows for removing interrupt jitter and delay as the counter always increments once per 80MHz clock. Changes to a waveform are contiguous and only take effect on the next waveform transition, @@ -19,8 +19,9 @@ This replaces older tone(), analogWrite(), and the Servo classes. - Everywhere in the code where "cycles" is used, it means ESP.getCycleTime() - cycles, not TIMER1 cycles (which may be 2 CPU clocks @ 160MHz). + Everywhere in the code where "cycles" is used, it means ESP.getCycleCount() + clock cycle count, or an interval measured in CPU clock cycles, but not TIMER1 + cycles (which may be 2 CPU clock cycles @ 160MHz). This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -50,8 +51,10 @@ extern "C" { // If runtimeUS > 0 then automatically stop it after that many usecs. // Returns true or false on success or failure. int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS); -// Same as above, but pass in CPU clock cycles instead of microseconds -int startWaveformCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles); +// Start or change a waveform of the specified high and low CPU clock cycles on specific pin. +// If runtimeCycles > 0 then automatically stop it after that many CPU clock cycles. +// Returns true or false on success or failure. +int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles); // Stop a waveform, if any, on the specified pin. // Returns true or false on success or failure. int stopWaveform(uint8_t pin); diff --git a/cores/esp8266/core_esp8266_wiring_pwm.cpp b/cores/esp8266/core_esp8266_wiring_pwm.cpp index b15b95f82..1ebdd64c8 100644 --- a/cores/esp8266/core_esp8266_wiring_pwm.cpp +++ b/cores/esp8266/core_esp8266_wiring_pwm.cpp @@ -25,7 +25,6 @@ #include "core_esp8266_waveform.h" extern "C" { -#include "user_interface.h" static uint32_t analogMap = 0; static int32_t analogScale = PWMRANGE; @@ -51,7 +50,7 @@ extern void __analogWrite(uint8_t pin, int val) { if (pin > 16) { return; } - uint32_t analogPeriod = (1000000L * system_get_cpu_freq()) / analogFreq; + uint32_t analogPeriod = microsecondsToClockCycles(1000000UL) / analogFreq; if (val < 0) { val = 0; } else if (val > analogScale) { @@ -63,13 +62,11 @@ extern void __analogWrite(uint8_t pin, int val) { uint32_t low = analogPeriod - high; pinMode(pin, OUTPUT); if (low == 0) { - stopWaveform(pin); digitalWrite(pin, HIGH); } else if (high == 0) { - stopWaveform(pin); digitalWrite(pin, LOW); } else { - if (startWaveformCycles(pin, high, low, 0)) { + if (startWaveformClockCycles(pin, high, low, 0)) { analogMap |= (1 << pin); } } diff --git a/libraries/Servo/src/Servo.cpp b/libraries/Servo/src/Servo.cpp index 57294102b..3dc2c7753 100644 --- a/libraries/Servo/src/Servo.cpp +++ b/libraries/Servo/src/Servo.cpp @@ -24,6 +24,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include #include "core_esp8266_waveform.h" +uint32_t Servo::_servoMap = 0; + // similiar to map but will have increased accuracy that provides a more // symetric api (call it and use result to reverse will provide the original value) int improved_map(int value, int minIn, int maxIn, int minOut, int maxOut) @@ -82,6 +84,7 @@ uint8_t Servo::attach(int pin, uint16_t minUs, uint16_t maxUs) void Servo::detach() { if (_attached) { + _servoMap &= ~(1 << _pin); stopWaveform(_pin); _attached = false; digitalWrite(_pin, LOW); @@ -105,7 +108,10 @@ void Servo::writeMicroseconds(int value) { _valueUs = value; if (_attached) { - startWaveform(_pin, _valueUs, REFRESH_INTERVAL - _valueUs, 0); + _servoMap &= ~(1 << _pin); + if (startWaveform(_pin, _valueUs, REFRESH_INTERVAL - _valueUs, 0)) { + _servoMap |= (1 << _pin); + } } } diff --git a/libraries/Servo/src/Servo.h b/libraries/Servo/src/Servo.h index c9a381174..45f593c0d 100644 --- a/libraries/Servo/src/Servo.h +++ b/libraries/Servo/src/Servo.h @@ -72,6 +72,7 @@ public: int readMicroseconds(); // returns current pulse width in microseconds for this servo (was read_us() in first release) bool attached(); // return true if this servo is attached, otherwise false private: + static uint32_t _servoMap; bool _attached; uint8_t _pin; uint16_t _minUs; From b02643e7fadc5c7d618613058f70835d9c16649a Mon Sep 17 00:00:00 2001 From: kugelkopf123 <45996965+kugelkopf123@users.noreply.github.com> Date: Tue, 21 Apr 2020 02:09:54 +0200 Subject: [PATCH 07/29] Tz update (#7234) * TZ update Added the possibility to set the timezone without using NTP. This is helpful to have the timezone advantages when using an external RTC. * Update time.cpp --- cores/esp8266/Arduino.h | 2 ++ cores/esp8266/time.cpp | 16 +++++++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/cores/esp8266/Arduino.h b/cores/esp8266/Arduino.h index a4e00ec73..edb6bdd5c 100644 --- a/cores/esp8266/Arduino.h +++ b/cores/esp8266/Arduino.h @@ -276,6 +276,8 @@ long secureRandom(long); long secureRandom(long, long); long map(long, long, long, long, long); +void setTZ(const char* tz); + void configTime(int timezone, int daylightOffset_sec, const char* server1, const char* server2 = nullptr, const char* server3 = nullptr); diff --git a/cores/esp8266/time.cpp b/cores/esp8266/time.cpp index 45481d91b..d6abbfc88 100644 --- a/cores/esp8266/time.cpp +++ b/cores/esp8266/time.cpp @@ -186,6 +186,14 @@ void configTime(int timezone_sec, int daylightOffset_sec, const char* server1, c /*** end of posix replacement ***/ } +void setTZ(const char* tz){ + + char tzram[strlen_P(tz) + 1]; + memcpy_P(tzram, tz, sizeof(tzram)); + setenv("TZ", tzram, 1/*overwrite*/); + tzset(); +} + void configTime(const char* tz, const char* server1, const char* server2, const char* server3) { sntp_stop(); @@ -193,10 +201,8 @@ void configTime(const char* tz, const char* server1, const char* server2, const setServer(0, server1); setServer(1, server2); setServer(2, server3); - char tzram[strlen_P(tz) + 1]; - memcpy_P(tzram, tz, sizeof(tzram)); - setenv("TZ", tzram, 1/*overwrite*/); - tzset(); - + setTZ(tz); + sntp_init(); } + From 368ca91869acbb7f388b090118b9f895a33a23a7 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Wed, 22 Apr 2020 16:34:45 -0700 Subject: [PATCH 08/29] Update to LittleFS v2.2.0 (#7240) --- libraries/LittleFS/lib/littlefs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/LittleFS/lib/littlefs b/libraries/LittleFS/lib/littlefs index ce2c01f09..a049f1318 160000 --- a/libraries/LittleFS/lib/littlefs +++ b/libraries/LittleFS/lib/littlefs @@ -1 +1 @@ -Subproject commit ce2c01f098f4d2b9479de5a796c3bb531f1fe14c +Subproject commit a049f1318eecbe502549f9d74a41951985fb956f From 2de9242b1ac4af12ff58fba83b9663e33024ecd1 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Wed, 22 Apr 2020 17:24:56 -0700 Subject: [PATCH 09/29] Add test for FS::open("w+") (#7241) Verify that a file is truncated on opening with w+ even if no data is written to it, for all FSes. Co-authored-by: Develo --- tests/host/fs/test_fs.inc | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/host/fs/test_fs.inc b/tests/host/fs/test_fs.inc index e6bedfa18..9a10e688f 100644 --- a/tests/host/fs/test_fs.inc +++ b/tests/host/fs/test_fs.inc @@ -161,6 +161,19 @@ TEST_CASE(TESTPRE "truncate", TESTPAT) REQUIRE( s == "some" ); } +TEST_CASE(TESTPRE "open(w+) truncates file", TESTPAT) +{ + FS_MOCK_DECLARE(64, 8, 512, ""); + REQUIRE(FSTYPE.begin()); + createFile("/file1", "some text"); + String s = readFile("/file1"); + REQUIRE( s == "some text"); + auto f = FSTYPE.open("/file1", "w+"); + f.close(); + String t = readFile("/file1"); + REQUIRE( t == ""); +} + #ifdef FS_HAS_DIRS #if FSTYPE != SDFS From 9b41d9ac5e50e7726010120fe9c265a8df1d7eea Mon Sep 17 00:00:00 2001 From: Mike Nix Date: Fri, 24 Apr 2020 00:15:21 +0800 Subject: [PATCH 10/29] XMC flash support - WIP (#6725) * Move the spi vendor list from Esp.h to its own header in eboot. * Fix ifdef issue with spi_vendors.h * Add initFlashQuirks() for any chip specific flash initialization. Called from user_init(). * namespace experimental for initFlashQuirks() * Slow down flash access during eboot firmware copy Part 1 - still some work to do * Slow down flash access during eboot firmware copy on XMC chips Part 2 - Identify the chip type. Note: there may still be issues with the access speed change. This is very much experimental. * Commit eboot.elf Co-authored-by: Develo Co-authored-by: Earle F. Philhower, III --- bootloaders/eboot/eboot.c | 62 ++++++++++++++ bootloaders/eboot/eboot.elf | Bin 34956 -> 36640 bytes bootloaders/eboot/spi_vendors.h | 63 +++++++++++++++ cores/esp8266/Esp.cpp | 6 ++ cores/esp8266/Esp.h | 38 +-------- cores/esp8266/core_esp8266_flash_quirks.cpp | 85 ++++++++++++++++++++ cores/esp8266/core_esp8266_main.cpp | 3 + cores/esp8266/flash_quirks.h | 42 ++++++++++ cores/esp8266/spi_flash_defs.h | 44 ++++++++++ cores/esp8266/spi_vendors.h | 40 +++++++++ 10 files changed, 346 insertions(+), 37 deletions(-) create mode 100644 bootloaders/eboot/spi_vendors.h create mode 100644 cores/esp8266/core_esp8266_flash_quirks.cpp create mode 100644 cores/esp8266/flash_quirks.h create mode 100644 cores/esp8266/spi_flash_defs.h create mode 100644 cores/esp8266/spi_vendors.h diff --git a/bootloaders/eboot/eboot.c b/bootloaders/eboot/eboot.c index c7cf285c0..39b14307a 100644 --- a/bootloaders/eboot/eboot.c +++ b/bootloaders/eboot/eboot.c @@ -12,6 +12,7 @@ #include #include "flash.h" #include "eboot_command.h" +#include "spi_vendors.h" #include extern unsigned char _gzip_dict; @@ -189,6 +190,28 @@ int copy_raw(const uint32_t src_addr, return 0; } +#define XMC_SUPPORT +#ifdef XMC_SUPPORT +// Define a few SPI0 registers we need access to +#define ESP8266_REG(addr) *((volatile uint32_t *)(0x60000000+(addr))) +#define SPI0CMD ESP8266_REG(0x200) +#define SPI0CLK ESP8266_REG(0x218) +#define SPI0C ESP8266_REG(0x208) +#define SPI0W0 ESP8266_REG(0x240) + +#define SPICMDRDID (1 << 28) + +/* spi_flash_get_id() + Returns the flash chip ID - same as the SDK function. + We need our own version as the SDK isn't available here. + */ +uint32_t __attribute__((noinline)) spi_flash_get_id() { + SPI0W0=0; + SPI0CMD=SPICMDRDID; + while (SPI0CMD) {} + return SPI0W0; +} +#endif // XMC_SUPPORT int main() { @@ -211,9 +234,48 @@ int main() if (cmd.action == ACTION_COPY_RAW) { ets_putc('c'); ets_putc('p'); ets_putc(':'); + +#ifdef XMC_SUPPORT + // save the flash access speed registers + uint32_t spi0clk = SPI0CLK; + uint32_t spi0c = SPI0C; + + uint32_t vendor = spi_flash_get_id() & 0x000000ff; + if (vendor == SPI_FLASH_VENDOR_XMC) { + uint32_t flashinfo=0; + if (SPIRead(0, &flashinfo, 4)) { + // failed to read the configured flash speed. + // Do not change anything, + } else { + // select an appropriate flash speed + // Register values are those used by ROM + switch ((flashinfo >> 24) & 0x0f) { + case 0x0: // 40MHz, slow to 20 + case 0x1: // 26MHz, slow to 20 + SPI0CLK = 0x00003043; + SPI0C = 0x00EAA313; + break; + case 0x2: // 20MHz, no change + break; + case 0xf: // 80MHz, slow to 26 + SPI0CLK = 0x00002002; + SPI0C = 0x00EAA202; + break; + default: + break; + } + } + } +#endif // XMC_SUPPORT ets_wdt_disable(); res = copy_raw(cmd.args[0], cmd.args[1], cmd.args[2]); ets_wdt_enable(); + +#ifdef XMC_SUPPORT + // restore the saved flash access speed registers + SPI0CLK = spi0clk; + SPI0C = spi0c; +#endif ets_putc('0'+res); ets_putc('\n'); if (res == 0) { cmd.action = ACTION_LOAD_APP; diff --git a/bootloaders/eboot/eboot.elf b/bootloaders/eboot/eboot.elf index 0d862f6a9f6bf2b014610c46d4ba14d4eb088fc0..bbd4452c0e5b418cb3a065daba765d68d3e541f2 100755 GIT binary patch literal 36640 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 literal 34956 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 - -// Vendor IDs taken from Flashrom project -// https://review.coreboot.org/cgit/flashrom.git/tree/flashchips.h?h=1.0.x -typedef enum { - SPI_FLASH_VENDOR_ALLIANCE = 0x52, /* Alliance Semiconductor */ - SPI_FLASH_VENDOR_AMD = 0x01, /* AMD */ - SPI_FLASH_VENDOR_AMIC = 0x37, /* AMIC */ - SPI_FLASH_VENDOR_ATMEL = 0x1F, /* Atmel (now used by Adesto) */ - SPI_FLASH_VENDOR_BRIGHT = 0xAD, /* Bright Microelectronics */ - SPI_FLASH_VENDOR_CATALYST = 0x31, /* Catalyst */ - SPI_FLASH_VENDOR_EON = 0x1C, /* EON Silicon Devices, missing 0x7F prefix */ - SPI_FLASH_VENDOR_ESMT = 0x8C, /* Elite Semiconductor Memory Technology (ESMT) / EFST Elite Flash Storage */ - SPI_FLASH_VENDOR_EXCEL = 0x4A, /* ESI, missing 0x7F prefix */ - SPI_FLASH_VENDOR_FIDELIX = 0xF8, /* Fidelix */ - SPI_FLASH_VENDOR_FUJITSU = 0x04, /* Fujitsu */ - SPI_FLASH_VENDOR_GIGADEVICE = 0xC8, /* GigaDevice */ - SPI_FLASH_VENDOR_HYUNDAI = 0xAD, /* Hyundai */ - SPI_FLASH_VENDOR_INTEL = 0x89, /* Intel */ - SPI_FLASH_VENDOR_ISSI = 0xD5, /* ISSI Integrated Silicon Solutions, see also PMC. */ - SPI_FLASH_VENDOR_MACRONIX = 0xC2, /* Macronix (MX) */ - SPI_FLASH_VENDOR_NANTRONICS = 0xD5, /* Nantronics, missing prefix */ - SPI_FLASH_VENDOR_PMC = 0x9D, /* PMC, missing 0x7F prefix */ - SPI_FLASH_VENDOR_PUYA = 0x85, /* Puya semiconductor (shanghai) co. ltd */ - SPI_FLASH_VENDOR_SANYO = 0x62, /* Sanyo */ - SPI_FLASH_VENDOR_SHARP = 0xB0, /* Sharp */ - SPI_FLASH_VENDOR_SPANSION = 0x01, /* Spansion, same ID as AMD */ - SPI_FLASH_VENDOR_SST = 0xBF, /* SST */ - SPI_FLASH_VENDOR_ST = 0x20, /* ST / SGS/Thomson / Numonyx (later acquired by Micron) */ - SPI_FLASH_VENDOR_SYNCMOS_MVC = 0x40, /* SyncMOS (SM) and Mosel Vitelic Corporation (MVC) */ - SPI_FLASH_VENDOR_TENX = 0x5E, /* Tenx Technologies */ - SPI_FLASH_VENDOR_TI = 0x97, /* Texas Instruments */ - SPI_FLASH_VENDOR_TI_OLD = 0x01, /* TI chips from last century */ - SPI_FLASH_VENDOR_WINBOND = 0xDA, /* Winbond */ - SPI_FLASH_VENDOR_WINBOND_NEX = 0xEF, /* Winbond (ex Nexcom) serial flashes */ - - SPI_FLASH_VENDOR_UNKNOWN = 0xFF -} SPI_FLASH_VENDOR_t; +#include "spi_vendors.h" /** * AVR macros for WDT managment diff --git a/cores/esp8266/core_esp8266_flash_quirks.cpp b/cores/esp8266/core_esp8266_flash_quirks.cpp new file mode 100644 index 000000000..7128fcfe2 --- /dev/null +++ b/cores/esp8266/core_esp8266_flash_quirks.cpp @@ -0,0 +1,85 @@ +/* + flash_quirks.cpp - Chip specific flash init + Copyright (c) 2019 Mike Nix. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + 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 +#include "spi_flash.h" + +#include "spi_utils.h" +#include "flash_quirks.h" + +#ifdef __cplusplus +extern "C" { +#endif + +namespace experimental { + +static int get_flash_mhz() { + // FIXME: copied from Esp.cpp - we really should define the magic values + uint32_t data; + uint8_t * bytes = (uint8_t *) &data; + // read first 4 byte (magic byte + flash config) + if(spi_flash_read(0x0000, &data, 4) == SPI_FLASH_RESULT_OK) { + switch (bytes[3] & 0x0F) { + case 0x0: // 40 MHz + return 40; + case 0x1: // 26 MHz + return 26; + case 0x2: // 20 MHz + return 20; + case 0xf: // 80 MHz + return 80; + default: // fail? + return 0; + } + } + return 0; +} + +/* initFlashQuirks() + * Do any chip-specific initialization to improve performance and reliability. + */ +void initFlashQuirks() { + using namespace experimental; + uint32_t vendor = spi_flash_get_id() & 0x000000ff; + + switch (vendor) { + case SPI_FLASH_VENDOR_XMC: + uint32_t SR3, newSR3; + if (SPI0Command(SPI_FLASH_CMD_RSR3, &SR3, 0, 8)==SPI_RESULT_OK) { // read SR3 + newSR3=SR3; + if (get_flash_mhz()>26) { // >26Mhz? + // Set the output drive to 100% + newSR3 &= ~(SPI_FLASH_SR3_XMC_DRV_MASK << SPI_FLASH_SR3_XMC_DRV_S); + newSR3 |= (SPI_FLASH_SR3_XMC_DRV_100 << SPI_FLASH_SR3_XMC_DRV_S); + } + if (newSR3 != SR3) { // only write if changed + if (SPI0Command(SPI_FLASH_CMD_WEVSR,NULL,0,0)==SPI_RESULT_OK) // write enable volatile SR + SPI0Command(SPI_FLASH_CMD_WSR3,&newSR3,8,0); // write to SR3 + SPI0Command(SPI_FLASH_CMD_WRDI,NULL,0,0); // write disable - probably not needed + } + } + } +} + +} // namespace experimental + +#ifdef __cplusplus +} +#endif diff --git a/cores/esp8266/core_esp8266_main.cpp b/cores/esp8266/core_esp8266_main.cpp index 0ec4f1f3b..2d8436234 100644 --- a/cores/esp8266/core_esp8266_main.cpp +++ b/cores/esp8266/core_esp8266_main.cpp @@ -34,6 +34,7 @@ extern "C" { } #include #include "gdb_hooks.h" +#include "flash_quirks.h" #define LOOP_TASK_PRIORITY 1 #define LOOP_QUEUE_SIZE 1 @@ -334,6 +335,8 @@ extern "C" void user_init(void) { initVariant(); + experimental::initFlashQuirks(); // Chip specific flash init. + cont_init(g_pcont); preinit(); // Prior to C++ Dynamic Init (not related to above init() ). Meant to be user redefinable. diff --git a/cores/esp8266/flash_quirks.h b/cores/esp8266/flash_quirks.h new file mode 100644 index 000000000..0d05c6d3e --- /dev/null +++ b/cores/esp8266/flash_quirks.h @@ -0,0 +1,42 @@ +/* + flash_quirks.h + Copyright (c) 2019 Mike Nix. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + 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 FLASH_QUIRKS_H +#define FLASH_QUIRKS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "spi_vendors.h" +#include "spi_flash_defs.h" + +namespace experimental { + +void initFlashQuirks(); + +} // namespace experimental + +#ifdef __cplusplus +} +#endif + + +#endif // FLASH_QUIRKS_H diff --git a/cores/esp8266/spi_flash_defs.h b/cores/esp8266/spi_flash_defs.h new file mode 100644 index 000000000..b749fc4c2 --- /dev/null +++ b/cores/esp8266/spi_flash_defs.h @@ -0,0 +1,44 @@ +/* + spi_flash_defs.h - SPI Flash chip commands and status registers + Copyright (c) 2019 Mike Nix. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + 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 SPI_FLASH_DEFS_H +#define SPI_FLASH_DEFS_H + +// Flash chip Status Register 3: Vendor XMC Output Drive levels +#define SPI_FLASH_SR3_XMC_DRV_25 1 +#define SPI_FLASH_SR3_XMC_DRV_50 0 +#define SPI_FLASH_SR3_XMC_DRV_75 2 +#define SPI_FLASH_SR3_XMC_DRV_100 3 + +#define SPI_FLASH_SR3_XMC_DRV_S 5 +#define SPI_FLASH_SR3_XMC_DRV_MASK 0x03 + +// Flash Chip commands +#define SPI_FLASH_CMD_RSR1 0x05 //Read Flash Status Register... +#define SPI_FLASH_CMD_RSR2 0x35 +#define SPI_FLASH_CMD_RSR3 0x15 +#define SPI_FLASH_CMD_WSR1 0x01 //Write Flash Status Register... +#define SPI_FLASH_CMD_WSR2 0x31 +#define SPI_FLASH_CMD_WSR3 0x11 +#define SPI_FLASH_CMD_WEVSR 0x50 //Write Enable Volatile Status Registers +#define SPI_FLASH_CMD_WREN 0x06 //Write Enable +#define SPI_FLASH_CMD_WRDI 0x04 //Write Disable + +#endif // SPI_FLASH_DEFS_H diff --git a/cores/esp8266/spi_vendors.h b/cores/esp8266/spi_vendors.h new file mode 100644 index 000000000..b656f4cb5 --- /dev/null +++ b/cores/esp8266/spi_vendors.h @@ -0,0 +1,40 @@ +/* + spi_vendors.h - Vendor IDs for SPI chips + Copyright (c) 2019 Mike Nix. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + 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 SPI_VENDORS_H +#define SPI_VENDORS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Definitions are placed in eboot. Include them here rather than duplicate them. + * Also, prefer to have eboot standalone as much as possible and have the core depend on it + * rather than have eboot depend on the core. + */ +#include <../../bootloaders/eboot/spi_vendors.h> + + +#ifdef __cplusplus +} +#endif + + +#endif // SPI_VENDORS_H From 36e047e908cfa6eafaaf824988070b49f2c2ff2a Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" <19971886+dok-net@users.noreply.github.com> Date: Fri, 24 Apr 2020 01:00:17 +0200 Subject: [PATCH 11/29] Allow stopWaveform to stop timed-out waveforms (#7236) Fixes #7230. --- cores/esp8266/core_esp8266_waveform.cpp | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index bd9667cf7..597a8e88a 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -178,17 +178,15 @@ int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) { } // If user sends in a pin >16 but <32, this will always point to a 0 bit // If they send >=32, then the shift will result in 0 and it will also return false - uint32_t mask = 1< microsecondsToClockCycles(10)) { - timer1_write(microsecondsToClockCycles(10)); - } - while (waveformToDisable) { - /* no-op */ // Can't delay() since stopWaveform may be called from an IRQ + if (waveformEnabled & (1UL << pin)) { + waveformToDisable = 1UL << pin; + // Must not interfere if Timer is due shortly + if (T1L > microsecondsToClockCycles(10)) { + timer1_write(microsecondsToClockCycles(10)); + } + while (waveformToDisable) { + /* no-op */ // Can't delay() since stopWaveform may be called from an IRQ + } } if (!waveformEnabled && !timer1CB) { deinitTimer(); From 1bb5ccf71e0e028a593238b68513aac553a00886 Mon Sep 17 00:00:00 2001 From: TheMaking <25956407+DieMaking@users.noreply.github.com> Date: Sun, 26 Apr 2020 19:58:22 +0200 Subject: [PATCH 12/29] Fixed misplacement of back quotes (#7247) It might be the thing in some other doc files but I only noticed it here --- doc/esp8266wifi/station-class.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/esp8266wifi/station-class.rst b/doc/esp8266wifi/station-class.rst index 77b148fc3..01dab377f 100644 --- a/doc/esp8266wifi/station-class.rst +++ b/doc/esp8266wifi/station-class.rst @@ -410,7 +410,7 @@ Get the DHCP hostname assigned to ESP station. WiFi.hostname() -Function returns ``String`` type. Default hostname is in format ``ESP_24xMAC``\ where 24xMAC are the last 24 bits of module's MAC address. +Function returns ``String`` type. Default hostname is in format ``ESP_24xMAC`` where 24xMAC are the last 24 bits of module's MAC address. The hostname may be changed using the following function: @@ -644,13 +644,13 @@ The Smart Config connection of an ESP module an access point is done by sniffing The following three functions are provided to implement Smart Config. -Start smart configuration mode by sniffing for special packets that contain SSID and password of desired Access Point. Depending on result either ``true`` or \`false is returned. +Start smart configuration mode by sniffing for special packets that contain SSID and password of desired Access Point. Depending on result either ``true`` or ``false`` is returned. .. code:: cpp beginSmartConfig() -Query Smart Config status, to decide when stop configuration. Function returns either ``true`` or ``false of``\ boolean\` type. +Query Smart Config status, to decide when stop configuration. Function returns either ``true`` or ``false`` of ``boolean`` type. .. code:: cpp From ec7644227ef19ebbf23a839154a878b7e9f7d577 Mon Sep 17 00:00:00 2001 From: geoffday67 Date: Mon, 27 Apr 2020 18:58:47 +0100 Subject: [PATCH 13/29] Hold transmitter in reset during rate change (#7248) --- cores/esp8266/core_esp8266_i2s.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/cores/esp8266/core_esp8266_i2s.cpp b/cores/esp8266/core_esp8266_i2s.cpp index 901ffe120..d783d1afc 100644 --- a/cores/esp8266/core_esp8266_i2s.cpp +++ b/cores/esp8266/core_esp8266_i2s.cpp @@ -464,14 +464,28 @@ void i2s_set_dividers(uint8_t div1, uint8_t div2) { div1 &= I2SBDM; div2 &= I2SCDM; + /* + Following this post: https://github.com/esp8266/Arduino/issues/2590 + We should reset the transmitter while changing the configuration bits to avoid random distortion. + */ + + uint32_t i2sc_temp = I2SC; + i2sc_temp |= (I2STXR); // Hold transmitter in reset + I2SC = i2sc_temp; + // trans master(active low), recv master(active_low), !bits mod(==16 bits/chanel), clear clock dividers - I2SC &= ~(I2STSM | I2SRSM | (I2SBMM << I2SBM) | (I2SBDM << I2SBD) | (I2SCDM << I2SCD)); + i2sc_temp &= ~(I2STSM | I2SRSM | (I2SBMM << I2SBM) | (I2SBDM << I2SBD) | (I2SCDM << I2SCD)); // I2SRF = Send/recv right channel first (? may be swapped form I2S spec of WS=0 => left) // I2SMR = MSB recv/xmit first // I2SRMS, I2STMS = 1-bit delay from WS to MSB (I2S format) // div1, div2 = Set I2S WS clock frequency. BCLK seems to be generated from 32x this - I2SC |= I2SRF | I2SMR | I2SRMS | I2STMS | (div1 << I2SBD) | (div2 << I2SCD); + i2sc_temp |= I2SRF | I2SMR | I2SRMS | I2STMS | (div1 << I2SBD) | (div2 << I2SCD); + + I2SC = i2sc_temp; + + i2sc_temp &= ~(I2STXR); // Release reset + I2SC = i2sc_temp; } float i2s_get_real_rate(){ From 3c9a75f8311633eb7b47c6940f397fe06db86c8c Mon Sep 17 00:00:00 2001 From: aerlon <39123666+aerlon@users.noreply.github.com> Date: Wed, 29 Apr 2020 03:25:10 +0200 Subject: [PATCH 14/29] Add CryptoInterface library (#6961) * - Add CryptoInterface library. - Add TypeConversion core files. * Fix compiler errors. - Make HelloCrypto.ino stylish. - Include assert.h in CryptoInterface.cpp. * - Move base36 arrays to PROGMEM in TypeConversionFunctions.cpp. - Add deprecated attribute to SHA1 and MD5 hashes. - Remove _warningsEnabled since this has been replaced by the deprecated attribute. - Prefix all getters with "get". - Move all CryptoInterface functionality to the experimental namespace. - Change formatting of core files. - Improve comments. * - Update keywords.txt. * - Remove WiFi.disconnect() from setup() in HelloCrypto example since it no longer seems to be required. * - Classify everything. - Remove delay in setup() from HelloCrypto example since it does not seem to be required to prevent missing initial Serial prints. - Mark type conversion functions as big endian. - Update keywords.txt. * - Remove namespace experimental. - Create ESP.random functions in the core based on the defaultNonceGenerator code, and use these in defaultNonceGenerator. - Rename CryptoInterface to esp8266::Crypto and move all functionality to the core. - Remove need to #include in the Crypto header file by changing br_hkdf_context to ::br_hkdf_context. - Restyle code files for core usage. * - Re-add namespace experimental. - Improve comments. * - Remove namespace esp8266. - Rename namespace Crypto to namespace crypto. Co-authored-by: Anders Co-authored-by: Develo --- cores/esp8266/Crypto.cpp | 552 ++++++++++++ cores/esp8266/Crypto.h | 845 ++++++++++++++++++ cores/esp8266/Esp.cpp | 57 ++ cores/esp8266/Esp.h | 3 + cores/esp8266/TypeConversion.cpp | 91 ++ cores/esp8266/TypeConversion.h | 80 ++ .../examples/HelloCrypto/HelloCrypto.ino | 97 ++ libraries/esp8266/keywords.txt | 31 + libraries/esp8266/library.properties | 2 +- 9 files changed, 1757 insertions(+), 1 deletion(-) create mode 100644 cores/esp8266/Crypto.cpp create mode 100644 cores/esp8266/Crypto.h create mode 100644 cores/esp8266/TypeConversion.cpp create mode 100644 cores/esp8266/TypeConversion.h create mode 100644 libraries/esp8266/examples/HelloCrypto/HelloCrypto.ino diff --git a/cores/esp8266/Crypto.cpp b/cores/esp8266/Crypto.cpp new file mode 100644 index 000000000..b2b40b3c0 --- /dev/null +++ b/cores/esp8266/Crypto.cpp @@ -0,0 +1,552 @@ +/* + BearSSL Copyright (c) 2016 Thomas Pornin + Rest of this file Copyright (C) 2019 Anders Löfgren + + License (MIT license): + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#include +#include "Crypto.h" +#include + +#include + +namespace TypeCast = esp8266::TypeConversion; + +namespace +{ +size_t _ctMinDataLength = 0; +size_t _ctMaxDataLength = 1024; + +uint8_t *defaultNonceGenerator(uint8_t *nonceArray, const size_t nonceLength) +{ + /** + The ESP32 Technical Reference Manual v4.1 chapter 24 has the following to say about random number generation (no information found for ESP8266): + + "When used correctly, every 32-bit value the system reads from the RNG_DATA_REG register of the random number generator is a true random number. + These true random numbers are generated based on the noise in the Wi-Fi/BT RF system. + When Wi-Fi and BT are disabled, the random number generator will give out pseudo-random numbers. + + When Wi-Fi or BT is enabled, the random number generator is fed two bits of entropy every APB clock cycle (normally 80 MHz). + Thus, for the maximum amount of entropy, it is advisable to read the random register at a maximum rate of 5 MHz. + A data sample of 2 GB, read from the random number generator with Wi-Fi enabled and the random register read at 5 MHz, + has been tested using the Dieharder Random Number Testsuite (version 3.31.1). + The sample passed all tests." + + Since ESP32 is the sequal to ESP8266 it is unlikely that the ESP8266 is able to generate random numbers more quickly than 5 MHz when run at a 80 MHz frequency. + A maximum random number frequency of 0.5 MHz is used here to leave some margin for possibly inferior components in the ESP8266. + It should be noted that the ESP8266 has no Bluetooth functionality, so turning the WiFi off is likely to cause RANDOM_REG32 to use pseudo-random numbers. + + It is possible that yield() must be called on the ESP8266 to properly feed the hardware random number generator new bits, since there is only one processor core available. + However, no feeding requirements are mentioned in the ESP32 documentation, and using yield() could possibly cause extended delays during nonce generation. + Thus only delayMicroseconds() is used below. + */ + + return ESP.random(nonceArray, nonceLength); +} + +experimental::crypto::nonceGeneratorType _nonceGenerator = defaultNonceGenerator; + +void *createBearsslHmac(const br_hash_class *hashType, const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength) +{ + // Comments mainly from https://www.bearssl.org/apidoc/bearssl__hmac_8h.html + + // HMAC is initialized with a key and an underlying hash function; it then fills a "key context". That context contains the processed key. + // With the key context, a HMAC context can be initialized to process the input bytes and obtain the MAC output. The key context is not modified during that process, and can be reused. + + br_hmac_key_context keyContext; // Holds general HMAC info + br_hmac_context hmacContext; // Holds general HMAC info + specific info for the current operation + + // HMAC key context initialisation. + // Initialise the key context with the provided hash key, using the hash function identified by hashType. This supports arbitrary key lengths. + br_hmac_key_init(&keyContext, hashType, hashKey, hashKeyLength); + + // Initialise a HMAC context with a key context. The key context is unmodified. + // Relevant data from the key context is immediately copied; the key context can thus be independently reused, modified or released without impacting this HMAC computation. + // An explicit output length can be specified; the actual output length will be the minimum of that value and the natural HMAC output length. + // If outputLength is 0, then the natural HMAC output length is selected. The "natural output length" is the output length of the underlying hash function. + br_hmac_init(&hmacContext, &keyContext, outputLength); + + // Provide the HMAC context with the data to create a HMAC from. + // The provided dataLength bytes are injected as extra input in the HMAC computation incarnated by the hmacContext. + // It is acceptable that dataLength is zero, in which case data is ignored (and may be NULL) and this function does nothing. + br_hmac_update(&hmacContext, data, dataLength); + + // Compute the HMAC output. + // The destination buffer MUST be large enough to accommodate the result; its length is at most the "natural length" of HMAC (i.e. the output length of the underlying hash function). + // The context is NOT modified; further bytes may be processed. Thus, "partial HMAC" values can be efficiently obtained. + // Optionally the constant-time version br_hmac_outCT() can be used. More info here: https://www.bearssl.org/constanttime.html . + br_hmac_out(&hmacContext, resultArray); // returns size_t outputLength + + return resultArray; +} + +String createBearsslHmac(const br_hash_class *hashType, const uint8_t hashTypeNaturalLength, const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) +{ + assert(1 <= hmacLength && hmacLength <= hashTypeNaturalLength); + + uint8_t hmac[hmacLength]; + createBearsslHmac(hashType, message.c_str(), message.length(), hashKey, hashKeyLength, hmac, hmacLength); + return TypeCast::uint8ArrayToHexString(hmac, hmacLength); +} + +void *createBearsslHmacCT(const br_hash_class *hashType, const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength) +{ + assert(_ctMinDataLength <= dataLength && dataLength <= _ctMaxDataLength); + + // Comments mainly from https://www.bearssl.org/apidoc/bearssl__hmac_8h.html + + // HMAC is initialized with a key and an underlying hash function; it then fills a "key context". That context contains the processed key. + // With the key context, a HMAC context can be initialized to process the input bytes and obtain the MAC output. The key context is not modified during that process, and can be reused. + + br_hmac_key_context keyContext; // Holds general HMAC info + br_hmac_context hmacContext; // Holds general HMAC info + specific info for the current operation + + // HMAC key context initialisation. + // Initialise the key context with the provided hash key, using the hash function identified by hashType. This supports arbitrary key lengths. + br_hmac_key_init(&keyContext, hashType, hashKey, hashKeyLength); + + // Initialise a HMAC context with a key context. The key context is unmodified. + // Relevant data from the key context is immediately copied; the key context can thus be independently reused, modified or released without impacting this HMAC computation. + // An explicit output length can be specified; the actual output length will be the minimum of that value and the natural HMAC output length. + // If outputLength is 0, then the natural HMAC output length is selected. The "natural output length" is the output length of the underlying hash function. + br_hmac_init(&hmacContext, &keyContext, outputLength); + + // Provide the HMAC context with the data to create a HMAC from. + // The provided dataLength bytes are injected as extra input in the HMAC computation incarnated by the hmacContext. + // It is acceptable that dataLength is zero, in which case data is ignored (and may be NULL) and this function does nothing. + // No need for br_hmac_update when using constant-time version it seems. If it is used, the data provided to br_hmac_outCT will just be appended. + // br_hmac_update(&hmacContext, data, dataLength); + + // Compute the HMAC output. Assumes message is minimum _ctMinDataLength bytes and maximum _ctMaxDataLength bytes. + // As long as this is true, the correct HMAC output is calculated in constant-time. More constant-time info here: https://www.bearssl.org/constanttime.html + // Some extra input bytes are processed, then the output is computed. + // The extra input consists in the dataLength bytes pointed to by data. The dataLength parameter must lie between _ctMinDataLength and _ctMaxDataLength (inclusive); + // _ctMaxDataLength bytes are actually read from data (indicating each data byte can be read multiple times, if dataLength < _ctMaxDataLength). + // Computing time (and memory access pattern) will not depend upon the data byte contents or the value of dataLength. + // The output is written in the resultArray buffer, that MUST be large enough to receive it. + // The difference _ctMaxDataLength - _ctMinDataLength MUST be less than 2^30 (i.e. about one gigabyte). + // This function computes the output properly only if the underlying hash function uses MD padding (i.e. MD5, SHA-1, SHA-224, SHA-256, SHA-384 or SHA-512). + // The provided context is NOT modified. + br_hmac_outCT(&hmacContext, data, dataLength, _ctMinDataLength, _ctMaxDataLength, resultArray); // returns size_t outputLength + + return resultArray; +} + +String createBearsslHmacCT(const br_hash_class *hashType, const uint8_t hashTypeNaturalLength, const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) +{ + assert(1 <= hmacLength && hmacLength <= hashTypeNaturalLength); + + uint8_t hmac[hmacLength]; + createBearsslHmacCT(hashType, message.c_str(), message.length(), hashKey, hashKeyLength, hmac, hmacLength); + return TypeCast::uint8ArrayToHexString(hmac, hmacLength); +} + + +// Helper function to avoid deprecated warnings. +void *md5HashHelper(const void *data, const size_t dataLength, void *resultArray) +{ + br_md5_context context; + br_md5_init(&context); + br_md5_update(&context, data, dataLength); + br_md5_out(&context, resultArray); + return resultArray; +} + +// Helper function to avoid deprecated warnings. +void *sha1HashHelper(const void *data, const size_t dataLength, void *resultArray) +{ + br_sha1_context context; + br_sha1_init(&context); + br_sha1_update(&context, data, dataLength); + br_sha1_out(&context, resultArray); + return resultArray; +} +} + +namespace experimental +{ +namespace crypto +{ +void setCtMinDataLength(const size_t ctMinDataLength) +{ + assert(getCtMaxDataLength() - ctMinDataLength <= CT_MAX_DIFF); + _ctMinDataLength = ctMinDataLength; +} +size_t getCtMinDataLength() +{ + return _ctMinDataLength; +} + +void setCtMaxDataLength(const size_t ctMaxDataLength) +{ + assert(ctMaxDataLength - getCtMinDataLength() <= CT_MAX_DIFF); + _ctMaxDataLength = ctMaxDataLength; +} +size_t getCtMaxDataLength() +{ + return _ctMaxDataLength; +} + +void setNonceGenerator(nonceGeneratorType nonceGenerator) +{ + _nonceGenerator = nonceGenerator; +} +nonceGeneratorType getNonceGenerator() +{ + return _nonceGenerator; +} + + +// #################### MD5 #################### + +// resultArray must have size MD5::NATURAL_LENGTH or greater +void *MD5::hash(const void *data, const size_t dataLength, void *resultArray) +{ + return md5HashHelper(data, dataLength, resultArray); +} + +String MD5::hash(const String &message) +{ + uint8_t hashArray[NATURAL_LENGTH]; + md5HashHelper(message.c_str(), message.length(), hashArray); + return TypeCast::uint8ArrayToHexString(hashArray, NATURAL_LENGTH); +} + +void *MD5::hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength) +{ + return createBearsslHmac(&br_md5_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, outputLength); +} + +String MD5::hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) +{ + return createBearsslHmac(&br_md5_vtable, NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength); +} + +void *MD5::hmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength) +{ + return createBearsslHmacCT(&br_md5_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, outputLength); +} + +String MD5::hmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) +{ + return createBearsslHmacCT(&br_md5_vtable, NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength); +} + + +// #################### SHA-1 #################### + +// resultArray must have size SHA1::NATURAL_LENGTH or greater +void *SHA1::hash(const void *data, const size_t dataLength, void *resultArray) +{ + return sha1HashHelper(data, dataLength, resultArray); +} + +String SHA1::hash(const String &message) +{ + uint8_t hashArray[NATURAL_LENGTH]; + sha1HashHelper(message.c_str(), message.length(), hashArray); + return TypeCast::uint8ArrayToHexString(hashArray, NATURAL_LENGTH); +} + +void *SHA1::hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength) +{ + return createBearsslHmac(&br_sha1_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, outputLength); +} + +String SHA1::hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) +{ + return createBearsslHmac(&br_sha1_vtable, NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength); +} + +void *SHA1::hmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength) +{ + return createBearsslHmacCT(&br_sha1_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, outputLength); +} + +String SHA1::hmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) +{ + return createBearsslHmacCT(&br_sha1_vtable, NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength); +} + + +// #################### SHA-224 #################### + +// resultArray must have size SHA224::NATURAL_LENGTH or greater +void *SHA224::hash(const void *data, const size_t dataLength, void *resultArray) +{ + br_sha224_context context; + br_sha224_init(&context); + br_sha224_update(&context, data, dataLength); + br_sha224_out(&context, resultArray); + return resultArray; +} + +String SHA224::hash(const String &message) +{ + uint8_t hashArray[NATURAL_LENGTH]; + hash(message.c_str(), message.length(), hashArray); + return TypeCast::uint8ArrayToHexString(hashArray, NATURAL_LENGTH); +} + +void *SHA224::hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength) +{ + return createBearsslHmac(&br_sha224_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, outputLength); +} + +String SHA224::hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) +{ + return createBearsslHmac(&br_sha224_vtable, NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength); +} + +void *SHA224::hmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength) +{ + return createBearsslHmacCT(&br_sha224_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, outputLength); +} + +String SHA224::hmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) +{ + return createBearsslHmacCT(&br_sha224_vtable, NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength); +} + + +// #################### SHA-256 #################### + +// resultArray must have size SHA256::NATURAL_LENGTH or greater +void *SHA256::hash(const void *data, const size_t dataLength, void *resultArray) +{ + br_sha256_context context; + br_sha256_init(&context); + br_sha256_update(&context, data, dataLength); + br_sha256_out(&context, resultArray); + return resultArray; +} + +String SHA256::hash(const String &message) +{ + uint8_t hashArray[NATURAL_LENGTH]; + hash(message.c_str(), message.length(), hashArray); + return TypeCast::uint8ArrayToHexString(hashArray, NATURAL_LENGTH); +} + +void *SHA256::hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength) +{ + return createBearsslHmac(&br_sha256_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, outputLength); +} + +String SHA256::hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) +{ + return createBearsslHmac(&br_sha256_vtable, NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength); +} + +void *SHA256::hmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength) +{ + return createBearsslHmacCT(&br_sha256_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, outputLength); +} + +String SHA256::hmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) +{ + return createBearsslHmacCT(&br_sha256_vtable, NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength); +} + + +// #################### SHA-384 #################### + +// resultArray must have size SHA384::NATURAL_LENGTH or greater +void *SHA384::hash(const void *data, const size_t dataLength, void *resultArray) +{ + br_sha384_context context; + br_sha384_init(&context); + br_sha384_update(&context, data, dataLength); + br_sha384_out(&context, resultArray); + return resultArray; +} + +String SHA384::hash(const String &message) +{ + uint8_t hashArray[NATURAL_LENGTH]; + hash(message.c_str(), message.length(), hashArray); + return TypeCast::uint8ArrayToHexString(hashArray, NATURAL_LENGTH); +} + +void *SHA384::hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength) +{ + return createBearsslHmac(&br_sha384_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, outputLength); +} + +String SHA384::hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) +{ + return createBearsslHmac(&br_sha384_vtable, NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength); +} + +void *SHA384::hmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength) +{ + return createBearsslHmacCT(&br_sha384_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, outputLength); +} + +String SHA384::hmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) +{ + return createBearsslHmacCT(&br_sha384_vtable, NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength); +} + + +// #################### SHA-512 #################### + +// resultArray must have size SHA512::NATURAL_LENGTH or greater +void *SHA512::hash(const void *data, const size_t dataLength, void *resultArray) +{ + br_sha512_context context; + br_sha512_init(&context); + br_sha512_update(&context, data, dataLength); + br_sha512_out(&context, resultArray); + return resultArray; +} + +String SHA512::hash(const String &message) +{ + uint8_t hashArray[NATURAL_LENGTH]; + hash(message.c_str(), message.length(), hashArray); + return TypeCast::uint8ArrayToHexString(hashArray, NATURAL_LENGTH); +} + +void *SHA512::hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength) +{ + return createBearsslHmac(&br_sha512_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, outputLength); +} + +String SHA512::hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) +{ + return createBearsslHmac(&br_sha512_vtable, NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength); +} + +void *SHA512::hmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength) +{ + return createBearsslHmacCT(&br_sha512_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, outputLength); +} + +String SHA512::hmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) +{ + return createBearsslHmacCT(&br_sha512_vtable, NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength); +} + + +// #################### MD5+SHA-1 #################### + +// resultArray must have size MD5SHA1::NATURAL_LENGTH or greater +void *MD5SHA1::hash(const void *data, const size_t dataLength, void *resultArray) +{ + br_md5sha1_context context; + br_md5sha1_init(&context); + br_md5sha1_update(&context, data, dataLength); + br_md5sha1_out(&context, resultArray); + return resultArray; +} + +String MD5SHA1::hash(const String &message) +{ + uint8_t hashArray[NATURAL_LENGTH]; + hash(message.c_str(), message.length(), hashArray); + return TypeCast::uint8ArrayToHexString(hashArray, NATURAL_LENGTH); +} + + +// #################### HKDF #################### + +HKDF::HKDF(const void *keyMaterial, const size_t keyMaterialLength, const void *salt, const size_t saltLength) +{ + init(keyMaterial, keyMaterialLength, salt, saltLength); +} + +void HKDF::init(const void *keyMaterial, const size_t keyMaterialLength, const void *salt, const size_t saltLength) +{ + // Comments mainly from https://www.bearssl.org/apidoc/bearssl__kdf_8h.html + + // Initialize an HKDF context, with a hash function, and the salt. This starts the HKDF-Extract process. + br_hkdf_init(&hkdfContext, &br_sha256_vtable, salt, saltLength); + + // Inject more input bytes. This function may be called repeatedly if the input data is provided by chunks, after br_hkdf_init() but before br_hkdf_flip(). + br_hkdf_inject(&hkdfContext, keyMaterial, keyMaterialLength); + + // End the HKDF-Extract process, and start the HKDF-Expand process. + br_hkdf_flip(&hkdfContext); +} + +size_t HKDF::produce(void *resultArray, const size_t outputLength, const void *info, const size_t infoLength) +{ + // Comments mainly from https://www.bearssl.org/apidoc/bearssl__kdf_8h.html + + // HKDF output production (HKDF-Expand). + // Produces more output bytes from the current state. This function may be called several times, but only after br_hkdf_flip(). + // Returned value is the number of actually produced bytes. The total output length is limited to 255 times the output length of the underlying hash function. + return br_hkdf_produce(&hkdfContext, info, infoLength, resultArray, outputLength); +} + + +// #################### Authenticated Encryption with Associated Data (AEAD) #################### + + +// #################### ChaCha20+Poly1305 AEAD #################### + +void chacha20Poly1305Kernel(const int encrypt, void *data, const size_t dataLength, const void *key, const void *keySalt, const size_t keySaltLength, + const void *nonce, void *tag, const void *aad, const size_t aadLength) +{ + if (keySalt == nullptr) + { + br_poly1305_ctmul32_run(key, nonce, data, dataLength, aad, aadLength, tag, br_chacha20_ct_run, encrypt); + } + else + { + HKDF hkdfInstance(key, ENCRYPTION_KEY_LENGTH, keySalt, keySaltLength); + uint8_t derivedEncryptionKey[ENCRYPTION_KEY_LENGTH] {0}; + hkdfInstance.produce(derivedEncryptionKey, ENCRYPTION_KEY_LENGTH); + br_poly1305_ctmul32_run(derivedEncryptionKey, nonce, data, dataLength, aad, aadLength, tag, br_chacha20_ct_run, encrypt); + } +} + +void ChaCha20Poly1305::encrypt(void *data, const size_t dataLength, const void *key, const void *keySalt, const size_t keySaltLength, + void *resultingNonce, void *resultingTag, const void *aad, const size_t aadLength) +{ + uint8_t *nonce = (uint8_t *)resultingNonce; + getNonceGenerator()(nonce, 12); + + chacha20Poly1305Kernel(1, data, dataLength, key, keySalt, keySaltLength, nonce, resultingTag, aad, aadLength); +} + +bool ChaCha20Poly1305::decrypt(void *data, const size_t dataLength, const void *key, const void *keySalt, const size_t keySaltLength, + const void *encryptionNonce, const void *encryptionTag, const void *aad, const size_t aadLength) +{ + const uint8_t *oldTag = (const uint8_t *)encryptionTag; + uint8_t newTag[16] {0}; + + chacha20Poly1305Kernel(0, data, dataLength, key, keySalt, keySaltLength, encryptionNonce, newTag, aad, aadLength); + + for (uint32_t i = 0; i < sizeof newTag; ++i) + { + if (newTag[i] != oldTag[i]) + { + return false; + } + } + + return true; +} +} +} diff --git a/cores/esp8266/Crypto.h b/cores/esp8266/Crypto.h new file mode 100644 index 000000000..435b4836a --- /dev/null +++ b/cores/esp8266/Crypto.h @@ -0,0 +1,845 @@ +/* + BearSSL Copyright (c) 2016 Thomas Pornin + Rest of this file Copyright (C) 2019 Anders Löfgren + + License (MIT license): + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifndef __ESP8266_ARDUINO_CRYPTO_H__ +#define __ESP8266_ARDUINO_CRYPTO_H__ + +#include + +namespace experimental +{ +namespace crypto +{ +/** + Regarding constant-time (CT) HMAC: + + Basically, constant-time algorithms makes it harder for attackers to learn things about your system based on the execution time of code. + Good intro here: https://www.bearssl.org/constanttime.html + + It should be noted that every HMAC is already partially constant-time. Quoting the link above: + "Hash functions implemented by BearSSL (MD5, SHA-1, SHA-224, SHA-256, SHA-384 and SHA-512) consist in bitwise logical operations and additions on 32-bit or 64-bit words, + naturally yielding constant-time operations. HMAC is naturally as constant-time as the underlying hash function. The size of the MACed data, and the size of the key, + may leak, though; only the contents are protected." + + For messages much smaller than getCtMaxDataLength(), constant-time processing takes substantially longer time to complete than a normal HMAC, + determined by the size of (getCtMaxDataLength() - getCtMinDataLength()). + Constant-time processing also sets limits on the data length. + + Making the fixed data length limits variable will generally defeat the purpose of using constant-time. + Using data that exceeds the fixed data length limits will create the wrong HMAC. +*/ + + +/** + The nonce generator should take an uint8_t array with a given size in bytes and fill it with the nonce. + The uint8_t array should then be returned by the nonce generator. +*/ +using nonceGeneratorType = std::function; + +constexpr uint8_t ENCRYPTION_KEY_LENGTH = 32; + +constexpr uint32_t CT_MAX_DIFF = 1073741823; // 2^30 - 1 + +/** + This function allows for fine-tuning of the specifications for the constant time calculations. + It should not be changed once a constant time function has been used at least once. + Otherwise the constant time will not be constant for the used functions. + + The difference getCtMaxDataLength() - getCtMinDataLength() MUST be less than 2^30 (i.e. about one gigabyte). +*/ +void setCtMinDataLength(const size_t ctMinDataLength); +/** + 0 by default. +*/ +size_t getCtMinDataLength(); + +/** + This function allows for fine-tuning of the specifications for the constant time calculations. + It should not be changed once a constant time function has been used at least once. + Otherwise the constant time will not be constant for the used functions. + + The difference getCtMaxDataLength() - getCtMinDataLength() MUST be less than 2^30 (i.e. about one gigabyte). +*/ +void setCtMaxDataLength(const size_t ctMaxDataLength); +/** + 1024 by default. +*/ +size_t getCtMaxDataLength(); + +/** + Set the nonce generator used by the Crypto functions. + + @param nonceGenerator The nonce generator to use. +*/ +void setNonceGenerator(nonceGeneratorType nonceGenerator); +nonceGeneratorType getNonceGenerator(); + + +// #################### MD5 #################### + +struct MD5 +{ + static constexpr uint8_t NATURAL_LENGTH = 16; + + /** + WARNING! The MD5 hash is broken in terms of attacker resistance. + Only use it in those cases where attacker resistance is not important. Prefer SHA-256 or higher otherwise. + + Create a MD5 hash of the data. The result will be NATURAL_LENGTH bytes long and stored in resultArray. + Uses the BearSSL cryptographic library. + + @param data The data array from which to create the hash. + @param dataLength The length of the data array in bytes. + @param resultArray The array wherein to store the resulting hash. MUST be be able to contain NATURAL_LENGTH bytes or more. + + @return A pointer to resultArray. + */ + static void *hash(const void *data, const size_t dataLength, void *resultArray) __attribute__((deprecated)); + + /** + WARNING! The MD5 hash is broken in terms of attacker resistance. + Only use it in those cases where attacker resistance is not important. Prefer SHA-256 or higher otherwise. + + Create a MD5 hash of the data. The result will be NATURAL_LENGTH bytes long and returned as a String in HEX format. + Uses the BearSSL cryptographic library. + + @param message The string from which to create the hash. + + @return A String with the generated hash in HEX format. + */ + static String hash(const String &message) __attribute__((deprecated)); + + /** + Create a MD5 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray. + Uses the BearSSL cryptographic library. + + @param data The data array from which to create the HMAC. + @param dataLength The length of the data array in bytes. + @param hashKey The hash key to use when creating the HMAC. + @param hashKeyLength The length of the hash key in bytes. + @param resultArray The array wherein to store the resulting HMAC. + @param outputLength The desired length of the generated HMAC, in bytes. Must fit within resultArray. If outputLength is greater than NATURAL_LENGTH, + the first (lowest index) NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + If outputLength is 0, then the natural HMAC output length is selected. + + @return A pointer to resultArray. + */ + static void *hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength); + + /** + Create a MD5 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. + Uses the BearSSL cryptographic library. + + @param message The string from which to create the HMAC. + @param hashKey The hash key to use when creating the HMAC. + @param hashKeyLength The length of the hash key in bytes. + @param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to NATURAL_LENGTH. + + @return A String with the generated HMAC in HEX format. + */ + static String hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength); + + /** + Create a MD5 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray. + Constant-time version. + Uses the BearSSL cryptographic library. + + @param data The data array from which to create the HMAC. + @param dataLength The length of the data array in bytes. Valid values are in the range [getCtMinDataLength(), getCtMaxDataLength()]. + @param hashKey The hash key to use when creating the HMAC. + @param hashKeyLength The length of the hash key in bytes. + @param resultArray The array wherein to store the resulting HMAC. + @param outputLength The desired length of the generated HMAC, in bytes. Must fit within resultArray. If outputLength is greater than NATURAL_LENGTH, + the first (lowest index) NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + If outputLength is 0, then the natural HMAC output length is selected. + + @return A pointer to resultArray. + */ + static void *hmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength); + + /** + Create a MD5 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. + Constant-time version. + Uses the BearSSL cryptographic library. + + @param message The string from which to create the HMAC. Must have a length in the range [getCtMinDataLength(), getCtMaxDataLength()]. + @param hashKey The hash key to use when creating the HMAC. + @param hashKeyLength The length of the hash key in bytes. + @param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to NATURAL_LENGTH. + + @return A String with the generated HMAC in HEX format. + */ + static String hmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength); +}; + + +// #################### SHA-1 #################### + +struct SHA1 +{ + static constexpr uint8_t NATURAL_LENGTH = 20; + + /** + WARNING! The SHA-1 hash is broken in terms of attacker resistance. + Only use it in those cases where attacker resistance is not important. Prefer SHA-256 or higher otherwise. + + Create a SHA1 hash of the data. The result will be NATURAL_LENGTH bytes long and stored in resultArray. + Uses the BearSSL cryptographic library. + + @param data The data array from which to create the hash. + @param dataLength The length of the data array in bytes. + @param resultArray The array wherein to store the resulting hash. MUST be be able to contain NATURAL_LENGTH bytes or more. + + @return A pointer to resultArray. + */ + static void *hash(const void *data, const size_t dataLength, void *resultArray) __attribute__((deprecated)); + + /** + WARNING! The SHA-1 hash is broken in terms of attacker resistance. + Only use it in those cases where attacker resistance is not important. Prefer SHA-256 or higher otherwise. + + Create a SHA1 hash of the data. The result will be NATURAL_LENGTH bytes long and returned as a String in HEX format. + Uses the BearSSL cryptographic library. + + @param message The string from which to create the hash. + + @return A String with the generated hash in HEX format. + */ + static String hash(const String &message) __attribute__((deprecated)); + + /** + Create a SHA1 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray. + Uses the BearSSL cryptographic library. + + @param data The data array from which to create the HMAC. + @param dataLength The length of the data array in bytes. + @param hashKey The hash key to use when creating the HMAC. + @param hashKeyLength The length of the hash key in bytes. + @param resultArray The array wherein to store the resulting HMAC. + @param outputLength The desired length of the generated HMAC, in bytes. Must fit within resultArray. If outputLength is greater than NATURAL_LENGTH, + the first (lowest index) NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + If outputLength is 0, then the natural HMAC output length is selected. + + @return A pointer to resultArray. + */ + static void *hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength); + + /** + Create a SHA1 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. + Uses the BearSSL cryptographic library. + + @param message The string from which to create the HMAC. + @param hashKey The hash key to use when creating the HMAC. + @param hashKeyLength The length of the hash key in bytes. + @param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to NATURAL_LENGTH. + + @return A String with the generated HMAC in HEX format. + */ + static String hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength); + + /** + Create a SHA1 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray. + Constant-time version. + Uses the BearSSL cryptographic library. + + @param data The data array from which to create the HMAC. + @param dataLength The length of the data array in bytes. Valid values are in the range [getCtMinDataLength(), getCtMaxDataLength()]. + @param hashKey The hash key to use when creating the HMAC. + @param hashKeyLength The length of the hash key in bytes. + @param resultArray The array wherein to store the resulting HMAC. + @param outputLength The desired length of the generated HMAC, in bytes. Must fit within resultArray. If outputLength is greater than NATURAL_LENGTH, + the first (lowest index) NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + If outputLength is 0, then the natural HMAC output length is selected. + + @return A pointer to resultArray. + */ + static void *hmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength); + + /** + Create a SHA1 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. + Constant-time version. + Uses the BearSSL cryptographic library. + + @param message The string from which to create the HMAC. Must have a length in the range [getCtMinDataLength(), getCtMaxDataLength()]. + @param hashKey The hash key to use when creating the HMAC. + @param hashKeyLength The length of the hash key in bytes. + @param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to NATURAL_LENGTH. + + @return A String with the generated HMAC in HEX format. + */ + static String hmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength); +}; + + +// #################### SHA-224 #################### + +struct SHA224 +{ + static constexpr uint8_t NATURAL_LENGTH = 28; + + /** + Create a SHA224 hash of the data. The result will be NATURAL_LENGTH bytes long and stored in resultArray. + Uses the BearSSL cryptographic library. + + @param data The data array from which to create the hash. + @param dataLength The length of the data array in bytes. + @param resultArray The array wherein to store the resulting hash. MUST be be able to contain NATURAL_LENGTH bytes or more. + + @return A pointer to resultArray. + */ + static void *hash(const void *data, const size_t dataLength, void *resultArray); + + /** + Create a SHA224 hash of the data. The result will be NATURAL_LENGTH bytes long and returned as a String in HEX format. + Uses the BearSSL cryptographic library. + + @param message The string from which to create the hash. + + @return A String with the generated hash in HEX format. + */ + static String hash(const String &message); + + /** + Create a SHA224 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray. + Uses the BearSSL cryptographic library. + + @param data The data array from which to create the HMAC. + @param dataLength The length of the data array in bytes. + @param hashKey The hash key to use when creating the HMAC. + @param hashKeyLength The length of the hash key in bytes. + @param resultArray The array wherein to store the resulting HMAC. + @param outputLength The desired length of the generated HMAC, in bytes. Must fit within resultArray. If outputLength is greater than NATURAL_LENGTH, + the first (lowest index) NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + If outputLength is 0, then the natural HMAC output length is selected. + + @return A pointer to resultArray. + */ + static void *hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength); + + /** + Create a SHA224 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. + Uses the BearSSL cryptographic library. + + @param message The string from which to create the HMAC. + @param hashKey The hash key to use when creating the HMAC. + @param hashKeyLength The length of the hash key in bytes. + @param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to NATURAL_LENGTH. + + @return A String with the generated HMAC in HEX format. + */ + static String hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength); + + /** + Create a SHA224 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray. + Constant-time version. + Uses the BearSSL cryptographic library. + + @param data The data array from which to create the HMAC. + @param dataLength The length of the data array in bytes. Valid values are in the range [getCtMinDataLength(), getCtMaxDataLength()]. + @param hashKey The hash key to use when creating the HMAC. + @param hashKeyLength The length of the hash key in bytes. + @param resultArray The array wherein to store the resulting HMAC. + @param outputLength The desired length of the generated HMAC, in bytes. Must fit within resultArray. If outputLength is greater than NATURAL_LENGTH, + the first (lowest index) NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + If outputLength is 0, then the natural HMAC output length is selected. + + @return A pointer to resultArray. + */ + static void *hmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength); + + /** + Create a SHA224 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. + Constant-time version. + Uses the BearSSL cryptographic library. + + @param message The string from which to create the HMAC. Must have a length in the range [getCtMinDataLength(), getCtMaxDataLength()]. + @param hashKey The hash key to use when creating the HMAC. + @param hashKeyLength The length of the hash key in bytes. + @param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to NATURAL_LENGTH. + + @return A String with the generated HMAC in HEX format. + */ + static String hmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength); +}; + + +// #################### SHA-256 #################### + +struct SHA256 +{ + static constexpr uint8_t NATURAL_LENGTH = 32; + + /** + Create a SHA256 hash of the data. The result will be NATURAL_LENGTH bytes long and stored in resultArray. + Uses the BearSSL cryptographic library. + + @param data The data array from which to create the hash. + @param dataLength The length of the data array in bytes. + @param resultArray The array wherein to store the resulting hash. MUST be be able to contain NATURAL_LENGTH bytes or more. + + @return A pointer to resultArray. + */ + static void *hash(const void *data, const size_t dataLength, void *resultArray); + + /** + Create a SHA256 hash of the data. The result will be NATURAL_LENGTH bytes long and returned as a String in HEX format. + Uses the BearSSL cryptographic library. + + @param message The string from which to create the hash. + + @return A String with the generated hash in HEX format. + */ + static String hash(const String &message); + + /** + Create a SHA256 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray. + Uses the BearSSL cryptographic library. + + @param data The data array from which to create the HMAC. + @param dataLength The length of the data array in bytes. + @param hashKey The hash key to use when creating the HMAC. + @param hashKeyLength The length of the hash key in bytes. + @param resultArray The array wherein to store the resulting HMAC. + @param outputLength The desired length of the generated HMAC, in bytes. Must fit within resultArray. If outputLength is greater than NATURAL_LENGTH, + the first (lowest index) NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + If outputLength is 0, then the natural HMAC output length is selected. + + @return A pointer to resultArray. + */ + static void *hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength); + + /** + Create a SHA256 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. + Uses the BearSSL cryptographic library. + + @param message The string from which to create the HMAC. + @param hashKey The hash key to use when creating the HMAC. + @param hashKeyLength The length of the hash key in bytes. + @param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to NATURAL_LENGTH. + + @return A String with the generated HMAC in HEX format. + */ + static String hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength); + + /** + Create a SHA256 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray. + Constant-time version. + Uses the BearSSL cryptographic library. + + @param data The data array from which to create the HMAC. + @param dataLength The length of the data array in bytes. Valid values are in the range [getCtMinDataLength(), getCtMaxDataLength()]. + @param hashKey The hash key to use when creating the HMAC. + @param hashKeyLength The length of the hash key in bytes. + @param resultArray The array wherein to store the resulting HMAC. + @param outputLength The desired length of the generated HMAC, in bytes. Must fit within resultArray. If outputLength is greater than NATURAL_LENGTH, + the first (lowest index) NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + If outputLength is 0, then the natural HMAC output length is selected. + + @return A pointer to resultArray. + */ + static void *hmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength); + + /** + Create a SHA256 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. + Constant-time version. + Uses the BearSSL cryptographic library. + + @param message The string from which to create the HMAC. Must have a length in the range [getCtMinDataLength(), getCtMaxDataLength()]. + @param hashKey The hash key to use when creating the HMAC. + @param hashKeyLength The length of the hash key in bytes. + @param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to NATURAL_LENGTH. + + @return A String with the generated HMAC in HEX format. + */ + static String hmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength); +}; + + +// #################### SHA-384 #################### + +struct SHA384 +{ + static constexpr uint8_t NATURAL_LENGTH = 48; + + /** + Create a SHA384 hash of the data. The result will be NATURAL_LENGTH bytes long and stored in resultArray. + Uses the BearSSL cryptographic library. + + @param data The data array from which to create the hash. + @param dataLength The length of the data array in bytes. + @param resultArray The array wherein to store the resulting hash. MUST be be able to contain NATURAL_LENGTH bytes or more. + + @return A pointer to resultArray. + */ + static void *hash(const void *data, const size_t dataLength, void *resultArray); + + /** + Create a SHA384 hash of the data. The result will be NATURAL_LENGTH bytes long and returned as a String in HEX format. + Uses the BearSSL cryptographic library. + + @param message The string from which to create the hash. + + @return A String with the generated hash in HEX format. + */ + static String hash(const String &message); + + /** + Create a SHA384 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray. + Uses the BearSSL cryptographic library. + + @param data The data array from which to create the HMAC. + @param dataLength The length of the data array in bytes. + @param hashKey The hash key to use when creating the HMAC. + @param hashKeyLength The length of the hash key in bytes. + @param resultArray The array wherein to store the resulting HMAC. + @param outputLength The desired length of the generated HMAC, in bytes. Must fit within resultArray. If outputLength is greater than NATURAL_LENGTH, + the first (lowest index) NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + If outputLength is 0, then the natural HMAC output length is selected. + + @return A pointer to resultArray. + */ + static void *hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength); + + /** + Create a SHA384 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. + Uses the BearSSL cryptographic library. + + @param message The string from which to create the HMAC. + @param hashKey The hash key to use when creating the HMAC. + @param hashKeyLength The length of the hash key in bytes. + @param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to NATURAL_LENGTH. + + @return A String with the generated HMAC in HEX format. + */ + static String hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength); + + /** + Create a SHA384 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray. + Constant-time version. + Uses the BearSSL cryptographic library. + + @param data The data array from which to create the HMAC. + @param dataLength The length of the data array in bytes. Valid values are in the range [getCtMinDataLength(), getCtMaxDataLength()]. + @param hashKey The hash key to use when creating the HMAC. + @param hashKeyLength The length of the hash key in bytes. + @param resultArray The array wherein to store the resulting HMAC. + @param outputLength The desired length of the generated HMAC, in bytes. Must fit within resultArray. If outputLength is greater than NATURAL_LENGTH, + the first (lowest index) NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + If outputLength is 0, then the natural HMAC output length is selected. + + @return A pointer to resultArray. + */ + static void *hmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength); + + /** + Create a SHA384 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. + Constant-time version. + Uses the BearSSL cryptographic library. + + @param message The string from which to create the HMAC. Must have a length in the range [getCtMinDataLength(), getCtMaxDataLength()]. + @param hashKey The hash key to use when creating the HMAC. + @param hashKeyLength The length of the hash key in bytes. + @param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to NATURAL_LENGTH. + + @return A String with the generated HMAC in HEX format. + */ + static String hmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength); +}; + + +// #################### SHA-512 #################### + +struct SHA512 +{ + static constexpr uint8_t NATURAL_LENGTH = 64; + + /** + Create a SHA512 hash of the data. The result will be NATURAL_LENGTH bytes long and stored in resultArray. + Uses the BearSSL cryptographic library. + + @param data The data array from which to create the hash. + @param dataLength The length of the data array in bytes. + @param resultArray The array wherein to store the resulting hash. MUST be be able to contain NATURAL_LENGTH bytes or more. + + @return A pointer to resultArray. + */ + static void *hash(const void *data, const size_t dataLength, void *resultArray); + + /** + Create a SHA512 hash of the data. The result will be NATURAL_LENGTH bytes long and returned as a String in HEX format. + Uses the BearSSL cryptographic library. + + @param message The string from which to create the hash. + + @return A String with the generated hash in HEX format. + */ + static String hash(const String &message); + + /** + Create a SHA512 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray. + Uses the BearSSL cryptographic library. + + @param data The data array from which to create the HMAC. + @param dataLength The length of the data array in bytes. + @param hashKey The hash key to use when creating the HMAC. + @param hashKeyLength The length of the hash key in bytes. + @param resultArray The array wherein to store the resulting HMAC. + @param outputLength The desired length of the generated HMAC, in bytes. Must fit within resultArray. If outputLength is greater than NATURAL_LENGTH, + the first (lowest index) NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + If outputLength is 0, then the natural HMAC output length is selected. + + @return A pointer to resultArray. + */ + static void *hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength); + + /** + Create a SHA512 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. + Uses the BearSSL cryptographic library. + + @param message The string from which to create the HMAC. + @param hashKey The hash key to use when creating the HMAC. + @param hashKeyLength The length of the hash key in bytes. + @param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to NATURAL_LENGTH. + + @return A String with the generated HMAC in HEX format. + */ + static String hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength); + + /** + Create a SHA512 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray. + Constant-time version. + Uses the BearSSL cryptographic library. + + @param data The data array from which to create the HMAC. + @param dataLength The length of the data array in bytes. Valid values are in the range [getCtMinDataLength(), getCtMaxDataLength()]. + @param hashKey The hash key to use when creating the HMAC. + @param hashKeyLength The length of the hash key in bytes. + @param resultArray The array wherein to store the resulting HMAC. + @param outputLength The desired length of the generated HMAC, in bytes. Must fit within resultArray. If outputLength is greater than NATURAL_LENGTH, + the first (lowest index) NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + If outputLength is 0, then the natural HMAC output length is selected. + + @return A pointer to resultArray. + */ + static void *hmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength); + + /** + Create a SHA512 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. + Constant-time version. + Uses the BearSSL cryptographic library. + + @param message The string from which to create the HMAC. Must have a length in the range [getCtMinDataLength(), getCtMaxDataLength()]. + @param hashKey The hash key to use when creating the HMAC. + @param hashKeyLength The length of the hash key in bytes. + @param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to NATURAL_LENGTH. + + @return A String with the generated HMAC in HEX format. + */ + static String hmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength); +}; + + +// #################### MD5+SHA-1 #################### + +struct MD5SHA1 +{ + static constexpr uint8_t NATURAL_LENGTH = 36; + + /** + Create a MD5+SHA-1 hash of the data. The result will be NATURAL_LENGTH bytes long and stored in resultArray. + Uses the BearSSL cryptographic library. + + MD5+SHA-1 is the concatenation of MD5 and SHA-1 computed over the same input; in the implementation, the internal data buffer is shared, + thus making it more memory-efficient than separate MD5 and SHA-1. It can be useful in implementing SSL 3.0, TLS 1.0 and TLS 1.1. + + @param data The data array from which to create the hash. + @param dataLength The length of the data array in bytes. + @param resultArray The array wherein to store the resulting hash. MUST be be able to contain NATURAL_LENGTH bytes or more. + + @return A pointer to resultArray. + */ + static void *hash(const void *data, const size_t dataLength, void *resultArray); + + /** + Create a MD5+SHA-1 hash of the data. The result will be NATURAL_LENGTH bytes long and returned as a String in HEX format. + Uses the BearSSL cryptographic library. + + MD5+SHA-1 is the concatenation of MD5 and SHA-1 computed over the same input; in the implementation, the internal data buffer is shared, + thus making it more memory-efficient than separate MD5 and SHA-1. It can be useful in implementing SSL 3.0, TLS 1.0 and TLS 1.1. + + @param message The string from which to create the hash. + + @return A String with the generated hash in HEX format. + */ + static String hash(const String &message); +}; + + +// #################### HKDF #################### + +struct HKDF +{ + /** + KDFs (key derivation functions) are functions that takes a variable length input, and provide a variable length output, meant to be used to derive subkeys from a master key. + HKDF is a KDF defined by RFC 5869. It is based on HMAC. The provided implementation uses SHA-256 as the underlying hash function. + + The constructor initializes the HKDF implementation with the input data to use for HKDF processing. (calls HKDF::init()) + + @param keyMaterial An array containing the key material to use when deriving subkeys. Typically this would be the master key. + @param keyMaterialLength The length of keyMaterial in bytes. + @param salt An array containing the salt to use when ingesting key material. Salt is non-secret and can be empty. + Its role is normally to bind the input to a conventional identifier that qualify it within the used protocol or application. + @param saltLength The length of the salt array, in bytes. + */ + HKDF(const void *keyMaterial, const size_t keyMaterialLength, const void *salt = nullptr, const size_t saltLength = 0); + + /** + This method initializes the HKDF implementation with the input data to use for HKDF processing. + Uses the BearSSL cryptographic library. + + @param keyMaterial An array containing the key material to use when deriving subkeys. Typically this would be the master key. + @param keyMaterialLength The length of keyMaterial in bytes. + @param salt An array containing the salt to use when ingesting key material. Salt is non-secret and can be empty. + Its role is normally to bind the input to a conventional identifier that qualify it within the used protocol or application. + @param saltLength The length of the salt array, in bytes. + */ + void init(const void *keyMaterial, const size_t keyMaterialLength, const void *salt = nullptr, const size_t saltLength = 0); + + /** + Produce more output bytes from the current HKDF state. This method may be called several times to obtain the full output by chunks. + The total output size is limited to 255 * SHA256::NATURAL_LENGTH bytes per unique HKDF::init()/constructor call. + Uses the BearSSL cryptographic library. + + @param resultArray The array wherein to store the resulting HKDF. + @param outputLength The requested number of bytes to fill with HKDF output in resultArray. + @param info NOTE: For correct HKDF processing, the same "info" string must be provided for every call until there's a new unique HKDF::init(). + An array containing the information string to use when producing output. Info is non-secret and can be empty. + Its role is normally to bind the output to a conventional identifier that qualify it within the used protocol or application. + @param infoLength The length of the info array, in bytes. + + @return The number of HKDF bytes actually produced. + */ + size_t produce(void *resultArray, const size_t outputLength, const void *info = nullptr, size_t infoLength = 0); + +private: + + // Use an opaque type to avoid #include which drags the lib declarations into userland. The global scope prefix is required for compilation to succeed, it seems. + ::br_hkdf_context hkdfContext; +}; + + +// #################### Authenticated Encryption with Associated Data (AEAD) #################### + +/** + From https://www.bearssl.org/apidoc/bearssl__aead_8h.html + + An AEAD algorithm processes messages and provides confidentiality (encryption) and checked integrity (MAC). It uses the following parameters: + + - A symmetric key. Exact size depends on the AEAD algorithm. + - A nonce (IV). Size depends on the AEAD algorithm; for most algorithms, it is crucial for security that any given nonce value is never used twice for the same key and distinct messages. + - Data to encrypt and protect. + - Additional authenticated data, which is covered by the MAC but otherwise left untouched (i.e. not encrypted). + + The AEAD algorithm encrypts the data, and produces an authentication tag. + It is assumed that the encrypted data, the tag, the additional authenticated data and the nonce are sent to the receiver; + the additional data and the nonce may be implicit (e.g. using elements of the underlying transport protocol, such as record sequence numbers). + The receiver will recompute the tag value and compare it with the one received; + if they match, then the data is correct, and can be decrypted and used; + otherwise, at least one of the elements was altered in transit, normally leading to wholesale rejection of the complete message. +*/ + + +// #################### ChaCha20+Poly1305 AEAD #################### + +struct ChaCha20Poly1305 +{ + /** + Encrypt the data array using the ChaCha20 stream cipher and use Poly1305 for message authentication. + The function generates in place an equal-length ChaCha20 encrypted version of the data array. + More information about this encryption standard can be found here: https://tools.ietf.org/html/rfc7539 , https://tools.ietf.org/html/rfc8439 + Uses the BearSSL cryptographic library. + + Encryption of small messages (up to a few hundred data bytes) takes around 0.5-1 ms with the default nonceGenerator, half of this without keySalt. + + The output values of ChaCha20Poly1305::encrypt should be passed as input values to ChaCha20Poly1305::decrypt. + + Note that a 12 byte nonce is generated via getNonceGenerator() every time ChaCha20Poly1305::encrypt is called. + If the same key and nonce combination is used more than once for distinct messages, the encryption will be broken, so keep the following in mind: + + By default the nonce is generated via the hardware random number generator of the ESP8266. + The entropy of this source may not be sufficient to avoid nonce collisions, so to further reduce the risk of encryption failure + it is recommended that a keySalt is always provided when using the default nonceGenerator. Using a keySalt will create a + pseudorandom subkey from the original key via HKDF, and use that for the encryption/decryption. + The same key + keySalt will always generate the same subkey. + + An alternative to using a keySalt is to change the nonceGenerator so that it does not rely on random numbers. + One way to do this would be to use a counter that guarantees the same key + nonce combination is never used. + This may not be easily achievable in all scenarios, however. + + @param data An array containing the data to encrypt. The encrypted data is generated in place, so when the function returns the data array will contain the encrypted data. + @param dataLength The length of the data array in bytes. + @param key The secret encryption key to use. Must be 32 bytes (ENCRYPTION_KEY_LENGTH) long. + @param keySalt The salt to use when generating a subkey from key. Note that the same salt must be used during decryption as during encryption. Set to nullptr to prevent subkey generation. + @param keySaltLength The length of keySalt in bytes. + @param resultingNonce The array that will store the nonce generated during encryption. Must be able to contain at least 12 bytes. The nonce is not secret and must be passed to the decryption function. + @param resultingTag The array that will store the message authentication tag generated during encryption. Must be able to contain at least 16 bytes. The tag is not secret and must be passed to the decryption function. + @param aad Additional authenticated data. This data will be covered by the Poly1305 MAC, but not encrypted. + You can include the unencrypted parts of your message as AAD to ensure that the encrypted content cannot + be re-sent with replaced unencrypted data by an attacker. + Defaults to nullptr. + @param aadLength The length of the aad array in bytes. Defaults to 0. + */ + static void encrypt(void *data, const size_t dataLength, const void *key, const void *keySalt, const size_t keySaltLength, void *resultingNonce, void *resultingTag, const void *aad = nullptr, const size_t aadLength = 0); + + /** + Decrypt the data array using the ChaCha20 stream cipher and use Poly1305 for message authentication. + The function generates in place an equal-length ChaCha20 decrypted version of the data array. + More information about this encryption standard can be found here: https://tools.ietf.org/html/rfc7539 , https://tools.ietf.org/html/rfc8439 + Uses the BearSSL cryptographic library. + + Decryption of small messages (up to a few hundred data bytes) takes around 0.5-1 ms, half of this without keySalt. + + The output values of ChaCha20Poly1305::encrypt should be passed as input values to ChaCha20Poly1305::decrypt. + + @param data An array containing the data to decrypt. The decrypted data is generated in place, so when the function returns the data array will contain the decrypted data. + @param dataLength The length of the data array in bytes. + @param key The secret encryption key to use. Must be 32 bytes (ENCRYPTION_KEY_LENGTH) long. + @param keySalt The salt to use when generating a subkey from key. Note that the same salt must be used during decryption as during encryption. Set to nullptr to prevent subkey generation. + @param keySaltLength The length of keySalt in bytes. + @param encryptionNonce An array containing the nonce that was generated during encryption. The nonce should be 12 bytes. + @param encryptionTag An array containing the message authentication tag that was generated during encryption. The tag should be 16 bytes. + @param aad Additional authenticated data. This data will be covered by the Poly1305 MAC, but not decrypted. + You can include the unencrypted parts of your message as AAD to ensure that the encrypted content cannot + be re-sent with replaced unencrypted data by an attacker. + Defaults to nullptr. + @param aadLength The length of the aad array in bytes. Defaults to 0. + + @return True if the decryption was successful (the generated tag matches encryptionTag). False otherwise. Note that the data array is modified regardless of this outcome. + */ + static bool decrypt(void *data, const size_t dataLength, const void *key, const void *keySalt, const size_t keySaltLength, const void *encryptionNonce, const void *encryptionTag, const void *aad = nullptr, const size_t aadLength = 0); +}; +} +} +#endif diff --git a/cores/esp8266/Esp.cpp b/cores/esp8266/Esp.cpp index dc9e069d9..f385220ee 100644 --- a/cores/esp8266/Esp.cpp +++ b/cores/esp8266/Esp.cpp @@ -522,6 +522,63 @@ bool EspClass::eraseConfig(void) { return true; } +uint8_t *EspClass::random(uint8_t *resultArray, const size_t outputSizeBytes) const +{ + /** + * The ESP32 Technical Reference Manual v4.1 chapter 24 has the following to say about random number generation (no information found for ESP8266): + * + * "When used correctly, every 32-bit value the system reads from the RNG_DATA_REG register of the random number generator is a true random number. + * These true random numbers are generated based on the noise in the Wi-Fi/BT RF system. + * When Wi-Fi and BT are disabled, the random number generator will give out pseudo-random numbers. + * + * When Wi-Fi or BT is enabled, the random number generator is fed two bits of entropy every APB clock cycle (normally 80 MHz). + * Thus, for the maximum amount of entropy, it is advisable to read the random register at a maximum rate of 5 MHz. + * A data sample of 2 GB, read from the random number generator with Wi-Fi enabled and the random register read at 5 MHz, + * has been tested using the Dieharder Random Number Testsuite (version 3.31.1). + * The sample passed all tests." + * + * Since ESP32 is the sequal to ESP8266 it is unlikely that the ESP8266 is able to generate random numbers more quickly than 5 MHz when run at a 80 MHz frequency. + * A maximum random number frequency of 0.5 MHz is used here to leave some margin for possibly inferior components in the ESP8266. + * It should be noted that the ESP8266 has no Bluetooth functionality, so turning the WiFi off is likely to cause RANDOM_REG32 to use pseudo-random numbers. + * + * It is possible that yield() must be called on the ESP8266 to properly feed the hardware random number generator new bits, since there is only one processor core available. + * However, no feeding requirements are mentioned in the ESP32 documentation, and using yield() could possibly cause extended delays during nonce generation. + * Thus only delayMicroseconds() is used below. + */ + + constexpr uint8_t cooldownMicros = 2; + static uint32_t lastCalledMicros = micros() - cooldownMicros; + + uint32_t randomNumber = 0; + + for(size_t byteIndex = 0; byteIndex < outputSizeBytes; ++byteIndex) + { + if(byteIndex % 4 == 0) + { + // Old random number has been used up (random number could be exactly 0, so we can't check for that) + + uint32_t timeSinceLastCall = micros() - lastCalledMicros; + if(timeSinceLastCall < cooldownMicros) + delayMicroseconds(cooldownMicros - timeSinceLastCall); + + randomNumber = RANDOM_REG32; + lastCalledMicros = micros(); + } + + resultArray[byteIndex] = randomNumber; + randomNumber >>= 8; + } + + return resultArray; +} + +uint32_t EspClass::random() const +{ + union { uint32_t b32; uint8_t b8[4]; } result; + random(result.b8, 4); + return result.b32; +} + uint32_t EspClass::getSketchSize() { static uint32_t result = 0; if (result) diff --git a/cores/esp8266/Esp.h b/cores/esp8266/Esp.h index a8fff3651..4ad59e223 100644 --- a/cores/esp8266/Esp.h +++ b/cores/esp8266/Esp.h @@ -164,6 +164,9 @@ class EspClass { bool eraseConfig(); + uint8_t *random(uint8_t *resultArray, const size_t outputSizeBytes) const; + uint32_t random() const; + #ifndef CORE_MOCK inline uint32_t getCycleCount() __attribute__((always_inline)); #else diff --git a/cores/esp8266/TypeConversion.cpp b/cores/esp8266/TypeConversion.cpp new file mode 100644 index 000000000..61a47858d --- /dev/null +++ b/cores/esp8266/TypeConversion.cpp @@ -0,0 +1,91 @@ +/* + TypeConversion functionality + Copyright (C) 2019 Anders Löfgren + + License (MIT license): + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#include +#include "TypeConversion.h" + +namespace esp8266 +{ +namespace TypeConversion +{ +const char base36Chars[36] PROGMEM = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}; +const uint8_t base36CharValues[75] PROGMEM {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, // 0 to 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, 0, 0, 0, 0, 0, 0, // Upper case letters + 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 // Lower case letters +}; + + +String uint8ArrayToHexString(const uint8_t *uint8Array, const uint32_t arrayLength) +{ + String hexString; + if (!hexString.reserve(2 * arrayLength)) // Each uint8_t will become two characters (00 to FF) + { + return emptyString; + } + + for (uint32_t i = 0; i < arrayLength; ++i) + { + hexString += (char)pgm_read_byte(base36Chars + (uint8Array[i] >> 4)); + hexString += (char)pgm_read_byte(base36Chars + uint8Array[i] % 16); + } + + return hexString; +} + +uint8_t *hexStringToUint8Array(const String &hexString, uint8_t *uint8Array, const uint32_t arrayLength) +{ + assert(hexString.length() >= arrayLength * 2); // Each array element can hold two hexString characters + + for (uint32_t i = 0; i < arrayLength; ++i) + { + uint8Array[i] = (pgm_read_byte(base36CharValues + hexString.charAt(i * 2) - '0') << 4) + pgm_read_byte(base36CharValues + hexString.charAt(i * 2 + 1) - '0'); + } + + return uint8Array; +} + +uint8_t *uint64ToUint8ArrayBE(const uint64_t value, uint8_t *resultArray) +{ + resultArray[7] = value; + resultArray[6] = value >> 8; + resultArray[5] = value >> 16; + resultArray[4] = value >> 24; + resultArray[3] = value >> 32; + resultArray[2] = value >> 40; + resultArray[1] = value >> 48; + resultArray[0] = value >> 56; + + return resultArray; +} + +uint64_t uint8ArrayToUint64BE(const uint8_t *inputArray) +{ + uint64_t result = (uint64_t)inputArray[0] << 56 | (uint64_t)inputArray[1] << 48 | (uint64_t)inputArray[2] << 40 | (uint64_t)inputArray[3] << 32 + | (uint64_t)inputArray[4] << 24 | (uint64_t)inputArray[5] << 16 | (uint64_t)inputArray[6] << 8 | (uint64_t)inputArray[7]; + + return result; +} +} +} diff --git a/cores/esp8266/TypeConversion.h b/cores/esp8266/TypeConversion.h new file mode 100644 index 000000000..6360eb9cb --- /dev/null +++ b/cores/esp8266/TypeConversion.h @@ -0,0 +1,80 @@ +/* + TypeConversion functionality + Copyright (C) 2019 Anders Löfgren + + License (MIT license): + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifndef __ESP8266_TYPECONVERSION_H__ +#define __ESP8266_TYPECONVERSION_H__ + +#include + +namespace esp8266 +{ +namespace TypeConversion +{ +extern const char base36Chars[36]; + +// Subtract '0' to normalize the char before lookup. +extern const uint8_t base36CharValues[75]; + +/** + Convert the contents of a uint8_t array to a String in HEX format. The resulting String starts from index 0 of the array. + All array elements will be padded with zeroes to ensure they are converted to 2 String characters each. + + @param uint8Array The array to make into a HEX String. + @param arrayLength The size of uint8Array, in bytes. + @return Normally a String containing the HEX representation of the uint8Array. An empty String if the memory allocation for the String failed. +*/ +String uint8ArrayToHexString(const uint8_t *uint8Array, const uint32_t arrayLength); + +/** + Convert the contents of a String in HEX format to a uint8_t array. Index 0 of the array will represent the start of the String. + There must be 2 String characters for each array element. Use padding with zeroes where required. + + @param hexString The HEX String to convert to a uint8_t array. Must contain at least 2*arrayLength characters. + @param uint8Array The array to fill with the contents of the hexString. + @param arrayLength The number of bytes to fill in uint8Array. + @return A pointer to the uint8Array. +*/ +uint8_t *hexStringToUint8Array(const String &hexString, uint8_t *uint8Array, const uint32_t arrayLength); + +/** + Takes a uint64_t value and stores the bits in a uint8_t array. Assumes index 0 of the array should contain MSB (big endian). + + @param value The uint64_t value to convert to a uint8_t array. + @param resultArray A uint8_t array that will hold the result once the function returns. Should have a size of at least 8 bytes. + @return The resultArray. +*/ +uint8_t *uint64ToUint8ArrayBE(const uint64_t value, uint8_t *resultArray); + +/** + Takes a uint8_t array and converts the first 8 (lowest index) elements to a uint64_t. Assumes index 0 of the array contains MSB (big endian). + + @param inputArray A uint8_t array containing the data to convert to a uint64_t. Should have a size of at least 8 bytes. + @return A uint64_t representation of the first 8 bytes of the array. +*/ +uint64_t uint8ArrayToUint64BE(const uint8_t *inputArray); +} +} + +#endif diff --git a/libraries/esp8266/examples/HelloCrypto/HelloCrypto.ino b/libraries/esp8266/examples/HelloCrypto/HelloCrypto.ino new file mode 100644 index 000000000..e6c4c493a --- /dev/null +++ b/libraries/esp8266/examples/HelloCrypto/HelloCrypto.ino @@ -0,0 +1,97 @@ +/** + This example demonstrates the usage of the ESP8266 Crypto implementation, which aims to contain easy-to-use cryptographic functions. + Crypto is currently primarily a frontend for the cryptographic library BearSSL which is used by `BearSSL::WiFiClientSecure` and `BearSSL::WiFiServerSecure` in the ESP8266 Arduino Core. + Extensive documentation can be found in the Crypto source code files and on the [BearSSL homepage](https://www.bearssl.org). +*/ + +#include +#include +#include + +namespace TypeCast = esp8266::TypeConversion; +using namespace experimental; + +/** + NOTE: Although we could define the strings below as normal String variables, + here we are using PROGMEM combined with the FPSTR() macro (and also just the F() macro further down in the file). + The reason is that this approach will place the strings in flash memory which will help save RAM during program execution. + Reading strings from flash will be slower than reading them from RAM, + but this will be a negligible difference when printing them to Serial. + + More on F(), FPSTR() and PROGMEM: + https://github.com/esp8266/Arduino/issues/1143 + https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html +*/ +constexpr char masterKey[] PROGMEM = "w86vn@rpfA O+S"; // Use 8 random characters or more + +void setup() { + // Prevents the flash memory from being worn out, see: https://github.com/esp8266/Arduino/issues/1054 . + // This will however delay node WiFi start-up by about 700 ms. The delay is 900 ms if we otherwise would have stored the WiFi network we want to connect to. + WiFi.persistent(false); + + Serial.begin(115200); + + Serial.println(); + Serial.println(); +} + +void loop() { + // This serves only to demonstrate the library use. See the header file for a full list of functions. + + String exampleData = F("Hello Crypto World!"); + Serial.println(String(F("This is our example data: ")) + exampleData); + + uint8_t resultArray[crypto::SHA256::NATURAL_LENGTH] { 0 }; + uint8_t derivedKey[crypto::ENCRYPTION_KEY_LENGTH] { 0 }; + + static uint32_t encryptionCounter = 0; + + + // Generate the salt to use for HKDF + uint8_t hkdfSalt[16] { 0 }; + crypto::getNonceGenerator()(hkdfSalt, sizeof hkdfSalt); + + // Generate the key to use for HMAC and encryption + crypto::HKDF hkdfInstance(FPSTR(masterKey), (sizeof masterKey) - 1, hkdfSalt, sizeof hkdfSalt); // (sizeof masterKey) - 1 removes the terminating null value of the c-string + hkdfInstance.produce(derivedKey, sizeof derivedKey); + + // Hash + crypto::SHA256::hash(exampleData.c_str(), exampleData.length(), resultArray); + Serial.println(String(F("\nThis is the SHA256 hash of our example data, in HEX format:\n")) + TypeCast::uint8ArrayToHexString(resultArray, sizeof resultArray)); + Serial.println(String(F("This is the SHA256 hash of our example data, in HEX format, using String output:\n")) + crypto::SHA256::hash(exampleData)); + + + // HMAC + // Note that HMAC output length is limited + crypto::SHA256::hmac(exampleData.c_str(), exampleData.length(), derivedKey, sizeof derivedKey, resultArray, sizeof resultArray); + Serial.println(String(F("\nThis is the SHA256 HMAC of our example data, in HEX format:\n")) + TypeCast::uint8ArrayToHexString(resultArray, sizeof resultArray)); + Serial.println(String(F("This is the SHA256 HMAC of our example data, in HEX format, using String output:\n")) + crypto::SHA256::hmac(exampleData, derivedKey, sizeof derivedKey, crypto::SHA256::NATURAL_LENGTH)); + + + // Authenticated Encryption with Associated Data (AEAD) + String dataToEncrypt = F("This data is not encrypted."); + uint8_t resultingNonce[12] { 0 }; // The nonce is always 12 bytes + uint8_t resultingTag[16] { 0 }; // The tag is always 16 bytes + + Serial.println(String(F("\nThis is the data to encrypt: ")) + dataToEncrypt); + + // Note that the key must be ENCRYPTION_KEY_LENGTH long. + crypto::ChaCha20Poly1305::encrypt(dataToEncrypt.begin(), dataToEncrypt.length(), derivedKey, &encryptionCounter, sizeof encryptionCounter, resultingNonce, resultingTag); + Serial.println(String(F("Encrypted data: ")) + dataToEncrypt); + + bool decryptionSucceeded = crypto::ChaCha20Poly1305::decrypt(dataToEncrypt.begin(), dataToEncrypt.length(), derivedKey, &encryptionCounter, sizeof encryptionCounter, resultingNonce, resultingTag); + encryptionCounter++; + + if (decryptionSucceeded) { + Serial.print(F("Decryption succeeded. Result: ")); + } else { + Serial.print(F("Decryption failed. Result: ")); + } + + Serial.println(dataToEncrypt); + + + Serial.println(F("\n##########################################################################################################\n")); + + delay(10000); +} diff --git a/libraries/esp8266/keywords.txt b/libraries/esp8266/keywords.txt index 902db205c..6d4587bb8 100644 --- a/libraries/esp8266/keywords.txt +++ b/libraries/esp8266/keywords.txt @@ -12,6 +12,18 @@ ESP KEYWORD1 +crypto KEYWORD1 +nonceGeneratorType KEYWORD1 +MD5 KEYWORD1 +SHA1 KEYWORD1 +SHA224 KEYWORD1 +SHA256 KEYWORD1 +SHA384 KEYWORD1 +SHA512 KEYWORD1 +MD5SHA1 KEYWORD1 +HKDF KEYWORD1 +ChaCha20Poly1305 KEYWORD1 + ####################################### # Methods and Functions (KEYWORD2) ####################################### @@ -60,6 +72,21 @@ getResetInfo KEYWORD2 getResetInfoPtr KEYWORD2 eraseConfig KEYWORD2 getCycleCount KEYWORD2 +random->KEYWORD2 + +setCtMinDataLength KEYWORD2 +getCtMinDataLength KEYWORD2 +setCtMaxDataLength KEYWORD2 +getCtMaxDataLength KEYWORD2 +setNonceGenerator KEYWORD2 +getNonceGenerator KEYWORD2 +hash KEYWORD2 +hmac KEYWORD2 +hmacCT KEYWORD2 +init KEYWORD2 +produce KEYWORD2 +encrypt KEYWORD2 +decrypt KEYWORD2 ####################################### # Constants (LITERAL1) @@ -79,6 +106,10 @@ WAKE_RF_DISABLED LITERAL1 ADC_VCC LITERAL1 ADC_TOUT LITERAL1 +NATURAL_LENGTH LITERAL1 +ENCRYPTION_KEY_LENGTH LITERAL1 +CT_MAX_DIFF LITERAL1 + ####################################### # namespace esp8266 ####################################### diff --git a/libraries/esp8266/library.properties b/libraries/esp8266/library.properties index 4c879f66a..2fc43d7e0 100644 --- a/libraries/esp8266/library.properties +++ b/libraries/esp8266/library.properties @@ -1,6 +1,6 @@ name=ESP8266 version=1.0 -author=Simon Peter,Markus Sattler,Ivan Grokhotkov +author=Anders Löfgren,Simon Peter,Markus Sattler,Ivan Grokhotkov maintainer=Ivan Grokhtkov sentence=ESP8266 sketches examples paragraph= From ce200ed72e5511d89d92998065df879f0cfcd6aa Mon Sep 17 00:00:00 2001 From: aerlon <39123666+aerlon@users.noreply.github.com> Date: Wed, 29 Apr 2020 16:30:26 +0200 Subject: [PATCH 15/29] - Move TypeConversion from namespace esp8266 to namespace experimental. (#7252) - Add using namespace experimental::crypto; to HelloCrypto.ino. - Add mention about new random function in libraries.rst. - Update keywords. Co-authored-by: Anders --- cores/esp8266/Crypto.cpp | 2 +- cores/esp8266/Esp.cpp | 2 +- cores/esp8266/TypeConversion.cpp | 2 +- cores/esp8266/TypeConversion.h | 2 +- doc/libraries.rst | 2 ++ .../examples/HelloCrypto/HelloCrypto.ino | 25 ++++++++++--------- libraries/esp8266/keywords.txt | 1 - 7 files changed, 19 insertions(+), 17 deletions(-) diff --git a/cores/esp8266/Crypto.cpp b/cores/esp8266/Crypto.cpp index b2b40b3c0..e51a29b8b 100644 --- a/cores/esp8266/Crypto.cpp +++ b/cores/esp8266/Crypto.cpp @@ -29,7 +29,7 @@ #include -namespace TypeCast = esp8266::TypeConversion; +namespace TypeCast = experimental::TypeConversion; namespace { diff --git a/cores/esp8266/Esp.cpp b/cores/esp8266/Esp.cpp index f385220ee..1127eef80 100644 --- a/cores/esp8266/Esp.cpp +++ b/cores/esp8266/Esp.cpp @@ -542,7 +542,7 @@ uint8_t *EspClass::random(uint8_t *resultArray, const size_t outputSizeBytes) co * It should be noted that the ESP8266 has no Bluetooth functionality, so turning the WiFi off is likely to cause RANDOM_REG32 to use pseudo-random numbers. * * It is possible that yield() must be called on the ESP8266 to properly feed the hardware random number generator new bits, since there is only one processor core available. - * However, no feeding requirements are mentioned in the ESP32 documentation, and using yield() could possibly cause extended delays during nonce generation. + * However, no feeding requirements are mentioned in the ESP32 documentation, and using yield() could possibly cause extended delays during number generation. * Thus only delayMicroseconds() is used below. */ diff --git a/cores/esp8266/TypeConversion.cpp b/cores/esp8266/TypeConversion.cpp index 61a47858d..371dee8ba 100644 --- a/cores/esp8266/TypeConversion.cpp +++ b/cores/esp8266/TypeConversion.cpp @@ -26,7 +26,7 @@ #include #include "TypeConversion.h" -namespace esp8266 +namespace experimental { namespace TypeConversion { diff --git a/cores/esp8266/TypeConversion.h b/cores/esp8266/TypeConversion.h index 6360eb9cb..522170eff 100644 --- a/cores/esp8266/TypeConversion.h +++ b/cores/esp8266/TypeConversion.h @@ -28,7 +28,7 @@ #include -namespace esp8266 +namespace experimental { namespace TypeConversion { diff --git a/doc/libraries.rst b/doc/libraries.rst index d99dfe71f..fc44d46f5 100644 --- a/doc/libraries.rst +++ b/doc/libraries.rst @@ -113,6 +113,8 @@ Some ESP-specific APIs related to deep sleep, RTC and flash memories are availab ``ESP.getCycleCount()`` returns the cpu instruction cycle count since start as an unsigned 32-bit. This is useful for accurate timing of very short actions like bit banging. +``ESP.random()`` should be used to generate true random numbers on the ESP. Returns an unsigned 32-bit integer with the random number. An alternate version is also available that fills an array of arbitrary length. Note that it seems as though the WiFi needs to be enabled to generate entropy for the random numbers, otherwise pseudo-random numbers are used. + ``ESP.checkFlashCRC()`` calculates the CRC of the program memory (not including any filesystems) and compares it to the one embedded in the image. If this call returns ``false`` then the flash has been corrupted. At that point, you may want to consider trying to send a MQTT message, to start a re-download of the application, blink a LED in an `SOS` pattern, etc. However, since the flash is known corrupted at this point there is no guarantee the app will be able to perform any of these operations, so in safety critical deployments an immediate shutdown to a fail-safe mode may be indicated. ``ESP.getVcc()`` may be used to measure supply voltage. ESP needs to reconfigure the ADC at startup in order for this feature to be available. Add the following line to the top of your sketch to use ``getVcc``: diff --git a/libraries/esp8266/examples/HelloCrypto/HelloCrypto.ino b/libraries/esp8266/examples/HelloCrypto/HelloCrypto.ino index e6c4c493a..1fb90134c 100644 --- a/libraries/esp8266/examples/HelloCrypto/HelloCrypto.ino +++ b/libraries/esp8266/examples/HelloCrypto/HelloCrypto.ino @@ -8,8 +8,7 @@ #include #include -namespace TypeCast = esp8266::TypeConversion; -using namespace experimental; +namespace TypeCast = experimental::TypeConversion; /** NOTE: Although we could define the strings below as normal String variables, @@ -38,34 +37,36 @@ void setup() { void loop() { // This serves only to demonstrate the library use. See the header file for a full list of functions. + using namespace experimental::crypto; + String exampleData = F("Hello Crypto World!"); Serial.println(String(F("This is our example data: ")) + exampleData); - uint8_t resultArray[crypto::SHA256::NATURAL_LENGTH] { 0 }; - uint8_t derivedKey[crypto::ENCRYPTION_KEY_LENGTH] { 0 }; + uint8_t resultArray[SHA256::NATURAL_LENGTH] { 0 }; + uint8_t derivedKey[ENCRYPTION_KEY_LENGTH] { 0 }; static uint32_t encryptionCounter = 0; // Generate the salt to use for HKDF uint8_t hkdfSalt[16] { 0 }; - crypto::getNonceGenerator()(hkdfSalt, sizeof hkdfSalt); + getNonceGenerator()(hkdfSalt, sizeof hkdfSalt); // Generate the key to use for HMAC and encryption - crypto::HKDF hkdfInstance(FPSTR(masterKey), (sizeof masterKey) - 1, hkdfSalt, sizeof hkdfSalt); // (sizeof masterKey) - 1 removes the terminating null value of the c-string + HKDF hkdfInstance(FPSTR(masterKey), (sizeof masterKey) - 1, hkdfSalt, sizeof hkdfSalt); // (sizeof masterKey) - 1 removes the terminating null value of the c-string hkdfInstance.produce(derivedKey, sizeof derivedKey); // Hash - crypto::SHA256::hash(exampleData.c_str(), exampleData.length(), resultArray); + SHA256::hash(exampleData.c_str(), exampleData.length(), resultArray); Serial.println(String(F("\nThis is the SHA256 hash of our example data, in HEX format:\n")) + TypeCast::uint8ArrayToHexString(resultArray, sizeof resultArray)); - Serial.println(String(F("This is the SHA256 hash of our example data, in HEX format, using String output:\n")) + crypto::SHA256::hash(exampleData)); + Serial.println(String(F("This is the SHA256 hash of our example data, in HEX format, using String output:\n")) + SHA256::hash(exampleData)); // HMAC // Note that HMAC output length is limited - crypto::SHA256::hmac(exampleData.c_str(), exampleData.length(), derivedKey, sizeof derivedKey, resultArray, sizeof resultArray); + SHA256::hmac(exampleData.c_str(), exampleData.length(), derivedKey, sizeof derivedKey, resultArray, sizeof resultArray); Serial.println(String(F("\nThis is the SHA256 HMAC of our example data, in HEX format:\n")) + TypeCast::uint8ArrayToHexString(resultArray, sizeof resultArray)); - Serial.println(String(F("This is the SHA256 HMAC of our example data, in HEX format, using String output:\n")) + crypto::SHA256::hmac(exampleData, derivedKey, sizeof derivedKey, crypto::SHA256::NATURAL_LENGTH)); + Serial.println(String(F("This is the SHA256 HMAC of our example data, in HEX format, using String output:\n")) + SHA256::hmac(exampleData, derivedKey, sizeof derivedKey, SHA256::NATURAL_LENGTH)); // Authenticated Encryption with Associated Data (AEAD) @@ -76,10 +77,10 @@ void loop() { Serial.println(String(F("\nThis is the data to encrypt: ")) + dataToEncrypt); // Note that the key must be ENCRYPTION_KEY_LENGTH long. - crypto::ChaCha20Poly1305::encrypt(dataToEncrypt.begin(), dataToEncrypt.length(), derivedKey, &encryptionCounter, sizeof encryptionCounter, resultingNonce, resultingTag); + ChaCha20Poly1305::encrypt(dataToEncrypt.begin(), dataToEncrypt.length(), derivedKey, &encryptionCounter, sizeof encryptionCounter, resultingNonce, resultingTag); Serial.println(String(F("Encrypted data: ")) + dataToEncrypt); - bool decryptionSucceeded = crypto::ChaCha20Poly1305::decrypt(dataToEncrypt.begin(), dataToEncrypt.length(), derivedKey, &encryptionCounter, sizeof encryptionCounter, resultingNonce, resultingTag); + bool decryptionSucceeded = ChaCha20Poly1305::decrypt(dataToEncrypt.begin(), dataToEncrypt.length(), derivedKey, &encryptionCounter, sizeof encryptionCounter, resultingNonce, resultingTag); encryptionCounter++; if (decryptionSucceeded) { diff --git a/libraries/esp8266/keywords.txt b/libraries/esp8266/keywords.txt index 6d4587bb8..702696646 100644 --- a/libraries/esp8266/keywords.txt +++ b/libraries/esp8266/keywords.txt @@ -12,7 +12,6 @@ ESP KEYWORD1 -crypto KEYWORD1 nonceGeneratorType KEYWORD1 MD5 KEYWORD1 SHA1 KEYWORD1 From 668b33ddf15e20051c6dc5b4a33525accf608706 Mon Sep 17 00:00:00 2001 From: vdeconinck Date: Wed, 29 Apr 2020 18:03:24 +0200 Subject: [PATCH 16/29] Revamp of the FSBrowser and SDWebServer examples (#7182) * Minimal file with a few ESP8266-specific keywords - github issue #3701 * Renamed "SDWebServer" to the more universal "WebFileManager" * SD was replaced by SDFS, and sketch now works on either SDFS, SPIFFS or LittleFS based on a #define logic (required adding a second param to open() and replacing 'FILE_WRITE' by "w") + Added size information to file list and a /status request handler to return filesystem status * Tree panel width is now proportional to window. Changed icons (lighter and more neutral), including one for files. Show size of files. Fill "filename" box upon clicking on a file. Sort files alphabetically. * Replaced by a lighter version * Return the filesystem time in the status object + Massive cleanup/merge/align with some code from the FSBrowser example and misc refactorings * Fixed folder handling * Replaced the FILESYSTEM #define by a filesystem variable, and introduced FSConfig to prevent FS formating. Fixed recursive deletion. Got rid of specific isDir() for SPIFFS. * Made 8.3 lowercase filenames formating optional (disabled by default). Refresh only part of the tree when possible. Selecting a file for upload defaults to the same folder as the last clicked file. Removed the Mkdir button on SPIFFS. * Added 'wait' cursor during asynchronous operations. Slight refactoring of XMLHttpRequest completion handling * Removed limitation "files must have an extension, folders may not". Case insensivity of the extension for the editor and preview. * Support Filenames without extension, Dirnames with extension. Added Save/Discard/Help buttons to Editor, discard confirmation on leave, and refresh tree/status upon save. Removed redundant Ctrl-Z + Ctrl-Shift-Z shortcut declarations. Small bug fixes. + some refactoring * Fixed tree refresh on delete in all cases by returning the remaining path as response to the delete request. Refactoring * Changed FS status in text by a percentage graph, with numbers as tooltip. Unsupported files on SPIFFS (files at root not sarting with "/", files with double "/", files ending with "/") are now detected and reported in the page. * Small fix + refactoring * Restrict filename support check to SPIFFS. * Implemented Move/Rename. Added "loading" screen during async operations (dim with spinner and status). Fixed "discard" feature that kept prompting even after an image was loaded. Improved refresh of parts of the tree, with recursive listing. Moved the "path" id attribute to the "li" elements for folders (was already the case for files). Refactoring and cleanup. * Fixed broken spinner * Cosmetic improvements. Removed non-functional Upload context menu. Fixed error in response to move requests. Added minified version. * Added specific icons for text and image files. Fixed incompatibilities with SPIFFS. Fixed a race condition between deletion and reinsertion of nodes when multiple folders are refreshed. Fixed missing URL decoding for files with special chars (e.g. space char). Moved info from source code comment to a readme.md file. Added source PNG to git. Cleanup. * Added favicon.ico. * Renamed project * Small changes * Add a note about the ace.js dependency * Minor changes * Define LittleFS by default. If both uncompressed and gz versions exist, use uncompressed version. Small fixes. * Define LittleFS by default. If both uncompressed and gz versions exist, use uncompressed version. Small fixes. * Restyled version * (dummy edit to retrigger broken CI) * Using unsigned int for comparison with String.length() * Return an error when upload fails (e.g. filesystem full) * Trying to reorder functions to please CI * Reordered functions to please CI. * Moved file * Renamed "SDWebServer" to the more universal "WebFileManager" * SD was replaced by SDFS, and sketch now works on either SDFS, SPIFFS or LittleFS based on a #define logic (required adding a second param to open() and replacing 'FILE_WRITE' by "w") + Added size information to file list and a /status request handler to return filesystem status * Tree panel width is now proportional to window. Changed icons (lighter and more neutral), including one for files. Show size of files. Fill "filename" box upon clicking on a file. Sort files alphabetically. * Replaced by a lighter version * Return the filesystem time in the status object + Massive cleanup/merge/align with some code from the FSBrowser example and misc refactorings * Fixed folder handling * Replaced the FILESYSTEM #define by a filesystem variable, and introduced FSConfig to prevent FS formating. Fixed recursive deletion. Got rid of specific isDir() for SPIFFS. * Made 8.3 lowercase filenames formating optional (disabled by default). Refresh only part of the tree when possible. Selecting a file for upload defaults to the same folder as the last clicked file. Removed the Mkdir button on SPIFFS. * Added 'wait' cursor during asynchronous operations. Slight refactoring of XMLHttpRequest completion handling * Removed limitation "files must have an extension, folders may not". Case insensivity of the extension for the editor and preview. * Support Filenames without extension, Dirnames with extension. Added Save/Discard/Help buttons to Editor, discard confirmation on leave, and refresh tree/status upon save. Removed redundant Ctrl-Z + Ctrl-Shift-Z shortcut declarations. Small bug fixes. + some refactoring * Fixed tree refresh on delete in all cases by returning the remaining path as response to the delete request. Refactoring * Changed FS status in text by a percentage graph, with numbers as tooltip. Unsupported files on SPIFFS (files at root not sarting with "/", files with double "/", files ending with "/") are now detected and reported in the page. * Small fix + refactoring * Restrict filename support check to SPIFFS. * Implemented Move/Rename. Added "loading" screen during async operations (dim with spinner and status). Fixed "discard" feature that kept prompting even after an image was loaded. Improved refresh of parts of the tree, with recursive listing. Moved the "path" id attribute to the "li" elements for folders (was already the case for files). Refactoring and cleanup. * Fixed broken spinner * Cosmetic improvements. Removed non-functional Upload context menu. Fixed error in response to move requests. Added minified version. * Added specific icons for text and image files. Fixed incompatibilities with SPIFFS. Fixed a race condition between deletion and reinsertion of nodes when multiple folders are refreshed. Fixed missing URL decoding for files with special chars (e.g. space char). Moved info from source code comment to a readme.md file. Added source PNG to git. Cleanup. * Added favicon.ico. * Renamed project * Small changes * Add a note about the ace.js dependency * Minor changes * Define LittleFS by default. If both uncompressed and gz versions exist, use uncompressed version. Small fixes. * Define LittleFS by default. If both uncompressed and gz versions exist, use uncompressed version. Small fixes. * Restyled version * (dummy edit to retrigger broken CI) * Using unsigned int for comparison with String.length() * Return an error when upload fails (e.g. filesystem full) * Trying to reorder functions to please CI * Reordered functions to please CI. * Update to use chunked response API * Removed temp files commited by mistake * Avoid using args() as requested * Use html entity for non-breaking space to avoid losing char when minifying * Script to preprocess index.htm * (reformated code) * (comments) * Preprocessed files * Fixed dump to create an actual include file * Optionally embed index.htm in code. (+ documentation and preprocessing script) * (reformated) * If editor cannot be loaded from the web, try a local version, or default to a text viewer if not present * (removed a TODO item :-)) * (forgot to reprocess files after last commit) * (reprocess should be ok this time) * Return error 500 when upload fails immediately (e.g. filesystem full) * Use standard tag for filesystem use * (updated following changes to index.htm) * Do not include gzipped version in the data folder by default. Leave it in the extras folder and change readme accordingly (plus some reformatingi of the readme file) * Gzipped index file not included in data/edit by default. It is now left in the extras folder. Readme file was updated accordingly (+ some reformating) * Reduce String clutter by reserving and concatenating elements one by one. * Use clear() to reset String. * Avoid comparisons against empty String. * Use char instead of single-char String where possible. * Prefer direct logic over inverted. * Rename returnBlah to replyBlah. * Renamed h2int to hexDigitToInt * Renamed getFileError() to checkForUnsupportedPath(), to avoid confusion with a getter. * Misc improvements. * Added comments about mandatory rebuilding gz and h files in case of update to index.htm. * Addressed a few comments. * Improve replies: bad requests vs server error * (reformated) * Reduce clutter by reserving String size beforehand. * Moved most Strings of more than 10 chars to flash. * Use lib version of urlDecode() instead of a local one, and only call it when required. * Added a comment about the dangers of recursion on embedded devices. * Added a more explicit warning in the .h header comment. * Added a typical set of required files to load ace editor from the ESP. * (reformated) * More explicit warning at the beginning of the .h version. Co-authored-by: david gauchard Co-authored-by: Earle F. Philhower, III Co-authored-by: Develo --- .../examples/FSBrowser/FSBrowser.ino | 739 ++++++++--- .../examples/FSBrowser/data/edit.htm.gz | Bin 4116 -> 0 bytes .../examples/FSBrowser/data/edit/index.htm | 1128 +++++++++++++++++ .../examples/FSBrowser/data/graphs.js.gz | Bin 1971 -> 0 bytes .../examples/FSBrowser/data/index.htm | 99 +- .../examples/FSBrowser/data/pins.png | Bin 0 -> 55578 bytes .../FSBrowser/extras/feathericons.png | Bin 0 -> 1558 bytes .../examples/FSBrowser/extras/index.htm.gz | Bin 0 -> 6261 bytes .../examples/FSBrowser/extras/index_htm.h | 529 ++++++++ .../examples/FSBrowser/extras/reduce_index.sh | 60 + .../examples/FSBrowser/readme.md | 138 ++ .../examples/SDWebServer/SDWebServer.ino | 320 ----- .../SDWebServer/SdRoot/edit/index.htm | 674 ---------- .../examples/SDWebServer/SdRoot/index.htm | 22 - .../examples/SDWebServer/SdRoot/pins.png | Bin 177869 -> 0 bytes 15 files changed, 2420 insertions(+), 1289 deletions(-) delete mode 100644 libraries/ESP8266WebServer/examples/FSBrowser/data/edit.htm.gz create mode 100644 libraries/ESP8266WebServer/examples/FSBrowser/data/edit/index.htm delete mode 100644 libraries/ESP8266WebServer/examples/FSBrowser/data/graphs.js.gz create mode 100644 libraries/ESP8266WebServer/examples/FSBrowser/data/pins.png create mode 100644 libraries/ESP8266WebServer/examples/FSBrowser/extras/feathericons.png create mode 100644 libraries/ESP8266WebServer/examples/FSBrowser/extras/index.htm.gz create mode 100644 libraries/ESP8266WebServer/examples/FSBrowser/extras/index_htm.h create mode 100755 libraries/ESP8266WebServer/examples/FSBrowser/extras/reduce_index.sh create mode 100644 libraries/ESP8266WebServer/examples/FSBrowser/readme.md delete mode 100644 libraries/ESP8266WebServer/examples/SDWebServer/SDWebServer.ino delete mode 100644 libraries/ESP8266WebServer/examples/SDWebServer/SdRoot/edit/index.htm delete mode 100644 libraries/ESP8266WebServer/examples/SDWebServer/SdRoot/index.htm delete mode 100644 libraries/ESP8266WebServer/examples/SDWebServer/SdRoot/pins.png diff --git a/libraries/ESP8266WebServer/examples/FSBrowser/FSBrowser.ino b/libraries/ESP8266WebServer/examples/FSBrowser/FSBrowser.ino index bd25fad58..caf863492 100644 --- a/libraries/ESP8266WebServer/examples/FSBrowser/FSBrowser.ino +++ b/libraries/ESP8266WebServer/examples/FSBrowser/FSBrowser.ino @@ -1,5 +1,6 @@ /* - FSWebServer - Example WebServer with SPIFFS backend for esp8266 + FSBrowser - A web-based FileSystem Browser for ESP8266 filesystems + Copyright (c) 2015 Hristo Gochkov. All rights reserved. This file is part of the ESP8266WebServer library for Arduino environment. @@ -7,30 +8,65 @@ 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 - upload the contents of the data folder with MkSPIFFS Tool ("ESP8266 Sketch Data Upload" in Tools menu in Arduino IDE) - or you can upload the contents of a folder if you CD in that folder and run the following command: - for file in `\ls -A1`; do curl -F "file=@$PWD/$file" esp8266fs.local/edit; done - - access the sample web page at http://esp8266fs.local - edit the page by going to http://esp8266fs.local/edit + See readme.md for more information. */ + +//////////////////////////////// + +// Select the FileSystem by uncommenting one of the lines below + +#define USE_SPIFFS +//#define USE_LITTLEFS +//#define USE_SDFS + +// Uncomment the following line to embed a version of the web page in the code +// (program code will be larger, but no file will have to be written to the filesystem). +// Note: the source file "extras/index_htm.h" must have been generated by "extras/reduce_index.sh" + +//#define INCLUDE_FALLBACK_INDEX_HTM + +//////////////////////////////// + #include #include #include #include -#include -#include +#include + +#ifdef INCLUDE_FALLBACK_INDEX_HTM +#include "extras/index_htm.h" +#endif + +#if defined USE_SPIFFS +#include +const char* fsName = "SPIFFS"; +FS* fileSystem = &SPIFFS; +SPIFFSConfig fileSystemConfig = SPIFFSConfig(); +#elif defined USE_LITTLEFS +#include +const char* fsName = "LittleFS"; +FS* fileSystem = &LittleFS; +LittleFSConfig fileSystemConfig = LittleFSConfig(); +#elif defined USE_SDFS +#include +const char* fsName = "SDFS"; +FS* fileSystem = &SDFS; +SDFSConfig fileSystemConfig = SDFSConfig(); +// fileSystemConfig.setCSPin(chipSelectPin); +#else +#error Please select a filesystem first by uncommenting one of the "#define USE_xxx" lines at the beginning of the sketch. +#endif -//FS* filesystem = &SPIFFS; -FS* filesystem = &LittleFS; #define DBG_OUTPUT_PORT Serial @@ -41,154 +77,166 @@ FS* filesystem = &LittleFS; const char* ssid = STASSID; const char* password = STAPSK; -const char* host = "esp8266fs"; +const char* host = "fsbrowser"; ESP8266WebServer server(80); -//holds the current upload -File fsUploadFile; -//format bytes -String formatBytes(size_t bytes) { - if (bytes < 1024) { - return String(bytes) + "B"; - } else if (bytes < (1024 * 1024)) { - return String(bytes / 1024.0) + "KB"; - } else if (bytes < (1024 * 1024 * 1024)) { - return String(bytes / 1024.0 / 1024.0) + "MB"; - } else { - return String(bytes / 1024.0 / 1024.0 / 1024.0) + "GB"; - } +static bool fsOK; +String unsupportedFiles = String(); + +File uploadFile; + +static const char TEXT_PLAIN[] PROGMEM = "text/plain"; +static const char FS_INIT_ERROR[] PROGMEM = "FS INIT ERROR"; +static const char FILE_NOT_FOUND[] PROGMEM = "FileNotFound"; + +//////////////////////////////// +// Utils to return HTTP codes, and determine content-type + +void replyOK() { + server.send(200, FPSTR(TEXT_PLAIN), ""); +} + +void replyOKWithMsg(String msg) { + server.send(200, FPSTR(TEXT_PLAIN), msg); +} + +void replyNotFound(String msg) { + server.send(404, FPSTR(TEXT_PLAIN), msg); +} + +void replyBadRequest(String msg) { + DBG_OUTPUT_PORT.println(msg); + server.send(400, FPSTR(TEXT_PLAIN), msg + "\r\n"); +} + +void replyServerError(String msg) { + DBG_OUTPUT_PORT.println(msg); + server.send(500, FPSTR(TEXT_PLAIN), msg + "\r\n"); } String getContentType(String filename) { - if (server.hasArg("download")) { - return "application/octet-stream"; - } else if (filename.endsWith(".htm")) { + if (filename.endsWith(".htm")) { return "text/html"; - } else if (filename.endsWith(".html")) { + } + if (filename.endsWith(".html")) { return "text/html"; - } else if (filename.endsWith(".css")) { + } + if (filename.endsWith(".css")) { return "text/css"; - } else if (filename.endsWith(".js")) { + } + if (filename.endsWith(".js")) { return "application/javascript"; - } else if (filename.endsWith(".png")) { + } + if (filename.endsWith(".png")) { return "image/png"; - } else if (filename.endsWith(".gif")) { + } + if (filename.endsWith(".gif")) { return "image/gif"; - } else if (filename.endsWith(".jpg")) { + } + if (filename.endsWith(".jpg")) { return "image/jpeg"; - } else if (filename.endsWith(".ico")) { + } + if (filename.endsWith(".jpeg")) { + return "image/jpeg"; + } + if (filename.endsWith(".ico")) { return "image/x-icon"; - } else if (filename.endsWith(".xml")) { + } + if (filename.endsWith(".xml")) { return "text/xml"; - } else if (filename.endsWith(".pdf")) { + } + if (filename.endsWith(".pdf")) { return "application/x-pdf"; - } else if (filename.endsWith(".zip")) { + } + if (filename.endsWith(".zip")) { return "application/x-zip"; - } else if (filename.endsWith(".gz")) { + } + if (filename.endsWith(".gz")) { return "application/x-gzip"; } - return "text/plain"; + return FPSTR(TEXT_PLAIN); } -bool handleFileRead(String path) { - DBG_OUTPUT_PORT.println("handleFileRead: " + path); - if (path.endsWith("/")) { - path += "index.htm"; +#ifdef USE_SPIFFS +/* + Checks filename for character combinations that are not supported by FSBrowser (alhtough valid on SPIFFS). + Returns an empty String if supported, or detail of error(s) if unsupported +*/ +String checkForUnsupportedPath(String filename) { + String error = String(); + if (!filename.startsWith("/")) { + error += F("!NO_LEADING_SLASH! "); } - String contentType = getContentType(path); - String pathWithGz = path + ".gz"; - if (filesystem->exists(pathWithGz) || filesystem->exists(path)) { - if (filesystem->exists(pathWithGz)) { - path += ".gz"; - } - File file = filesystem->open(path, "r"); - server.streamFile(file, contentType); - file.close(); - return true; + if (filename.indexOf("//") != -1) { + error += F("!DOUBLE_SLASH! "); } - return false; + if (filename.endsWith("/")) { + error += F("!TRAILING_SLASH! "); + } + return error; } +#endif -void handleFileUpload() { - if (server.uri() != "/edit") { - return; - } - HTTPUpload& upload = server.upload(); - if (upload.status == UPLOAD_FILE_START) { - String filename = upload.filename; - if (!filename.startsWith("/")) { - filename = "/" + filename; - } - DBG_OUTPUT_PORT.print("handleFileUpload Name: "); DBG_OUTPUT_PORT.println(filename); - fsUploadFile = filesystem->open(filename, "w"); - filename.clear(); - } else if (upload.status == UPLOAD_FILE_WRITE) { - //DBG_OUTPUT_PORT.print("handleFileUpload Data: "); DBG_OUTPUT_PORT.println(upload.currentSize); - if (fsUploadFile) { - fsUploadFile.write(upload.buf, upload.currentSize); - } - } else if (upload.status == UPLOAD_FILE_END) { - if (fsUploadFile) { - fsUploadFile.close(); - } - DBG_OUTPUT_PORT.print("handleFileUpload Size: "); DBG_OUTPUT_PORT.println(upload.totalSize); - } -} -void handleFileDelete() { - if (server.args() == 0) { - return server.send(500, "text/plain", "BAD ARGS"); - } - String path = server.arg(0); - DBG_OUTPUT_PORT.println("handleFileDelete: " + path); - if (path == "/") { - return server.send(500, "text/plain", "BAD PATH"); - } - if (!filesystem->exists(path)) { - return server.send(404, "text/plain", "FileNotFound"); - } - filesystem->remove(path); - server.send(200, "text/plain", ""); - path.clear(); -} +//////////////////////////////// +// Request handlers -void handleFileCreate() { - if (server.args() == 0) { - return server.send(500, "text/plain", "BAD ARGS"); - } - String path = server.arg(0); - DBG_OUTPUT_PORT.println("handleFileCreate: " + path); - if (path == "/") { - return server.send(500, "text/plain", "BAD PATH"); - } - if (filesystem->exists(path)) { - return server.send(500, "text/plain", "FILE EXISTS"); - } - File file = filesystem->open(path, "w"); - if (file) { - file.close(); +/* + Return the FS type, status and size info +*/ +void handleStatus() { + DBG_OUTPUT_PORT.println("handleStatus"); + FSInfo fs_info; + String json; + json.reserve(128); + + json = "{\"type\":\""; + json += fsName; + json += "\", \"isOk\":"; + if (fsOK) { + fileSystem->info(fs_info); + json += F("\"true\", \"totalBytes\":\""); + json += fs_info.totalBytes; + json += F("\", \"usedBytes\":\""); + json += fs_info.usedBytes; + json += "\""; } else { - return server.send(500, "text/plain", "CREATE FAILED"); + json += "\"false\""; } - server.send(200, "text/plain", ""); - path.clear(); + json += F(",\"unsupportedFiles\":\""); + json += unsupportedFiles; + json += "\"}"; + + server.send(200, "application/json", json); } + +/* + Return the list of files in the directory specified by the "dir" query string parameter. + Also demonstrates the use of chuncked responses. +*/ void handleFileList() { + if (!fsOK) { + return replyServerError(FPSTR(FS_INIT_ERROR)); + } + if (!server.hasArg("dir")) { - server.send(500, "text/plain", "BAD ARGS"); - return; + return replyBadRequest(F("DIR ARG MISSING")); } String path = server.arg("dir"); - DBG_OUTPUT_PORT.println("handleFileList: " + path); - Dir dir = filesystem->openDir(path); + if (path != "/" && !fileSystem->exists(path)) { + return replyBadRequest("BAD PATH"); + } + + DBG_OUTPUT_PORT.println(String("handleFileList: ") + path); + Dir dir = fileSystem->openDir(path); path.clear(); // use HTTP/1.1 Chunked response to avoid building a huge temporary string if (!server.chunkedResponseModeStart(200, "text/json")) { - server.send(505, FPSTR("text/html"), FPSTR("HTTP1.1 required")); + server.send(505, F("text/html"), F("HTTP1.1 required")); return; } @@ -196,7 +244,13 @@ void handleFileList() { String output; output.reserve(64); while (dir.next()) { - +#ifdef USE_SPIFFS + String error = checkForUnsupportedPath(dir.fileName()); + if (error.length() > 0) { + DBG_OUTPUT_PORT.println(String("Ignoring ") + error + dir.fileName()); + continue; + } +#endif if (output.length()) { // send string from previous iteration // as an HTTP chunk @@ -206,18 +260,23 @@ void handleFileList() { output = '['; } - File entry = dir.openFile("r"); - bool isDir = false; output += "{\"type\":\""; - output += (isDir) ? "dir" : "file"; - output += "\",\"name\":\""; - if (entry.name()[0] == '/') { - output += &(entry.name()[1]); + if (dir.isDirectory()) { + output += "dir"; } else { - output += entry.name(); + output += F("file\",\"size\":\""); + output += dir.fileSize(); } + + output += F("\",\"name\":\""); + // Always return names without leading "/" + if (dir.fileName()[0] == '/') { + output += &(dir.fileName()[1]); + } else { + output += dir.fileName(); + } + output += "\"}"; - entry.close(); } // send last string @@ -226,85 +285,393 @@ void handleFileList() { server.chunkedResponseFinalize(); } -void setup(void) { - DBG_OUTPUT_PORT.begin(115200); - DBG_OUTPUT_PORT.print("\n"); - DBG_OUTPUT_PORT.setDebugOutput(true); - filesystem->begin(); - { - Dir dir = filesystem->openDir("/"); - while (dir.next()) { - String fileName = dir.fileName(); - size_t fileSize = dir.fileSize(); - DBG_OUTPUT_PORT.printf("FS File: %s, size: %s\n", fileName.c_str(), formatBytes(fileSize).c_str()); + +/* + Read the given file from the filesystem and stream it back to the client +*/ +bool handleFileRead(String path) { + DBG_OUTPUT_PORT.println(String("handleFileRead: ") + path); + if (!fsOK) { + replyServerError(FPSTR(FS_INIT_ERROR)); + return true; + } + + if (path.endsWith("/")) { + path += "index.htm"; + } + + String contentType; + if (server.hasArg("download")) { + contentType = F("application/octet-stream"); + } else { + contentType = getContentType(path); + } + + if (!fileSystem->exists(path)) { + // File not found, try gzip version + path = path + ".gz"; + } + if (fileSystem->exists(path)) { + File file = fileSystem->open(path, "r"); + if (server.streamFile(file, contentType) != file.size()) { + DBG_OUTPUT_PORT.println("Sent less data than expected!"); } - DBG_OUTPUT_PORT.printf("\n"); + file.close(); + return true; } + return false; +} - //WIFI INIT + +/* + As some FS (e.g. LittleFS) delete the parent folder when the last child has been removed, + return the path of the closest parent still existing +*/ +String lastExistingParent(String path) { + while (!path.isEmpty() && !fileSystem->exists(path)) { + if (path.lastIndexOf('/') > 0) { + path = path.substring(0, path.lastIndexOf('/')); + } else { + path = String(); // No slash => the top folder does not exist + } + } + DBG_OUTPUT_PORT.println(String("Last existing parent: ") + path); + return path; +} + +/* + Handle the creation/rename of a new file + Operation | req.responseText + ---------------+-------------------------------------------------------------- + Create file | parent of created file + Create folder | parent of created folder + Rename file | parent of source file + Move file | parent of source file, or remaining ancestor + Rename folder | parent of source folder + Move folder | parent of source folder, or remaining ancestor +*/ +void handleFileCreate() { + if (!fsOK) { + return replyServerError(FPSTR(FS_INIT_ERROR)); + } + + String path = server.arg("path"); + if (path.isEmpty()) { + return replyBadRequest(F("PATH ARG MISSING")); + } + +#ifdef USE_SPIFFS + if (checkForUnsupportedPath(path).length() > 0) { + return replyServerError(F("INVALID FILENAME")); + } +#endif + + if (path == "/") { + return replyBadRequest("BAD PATH"); + } + if (fileSystem->exists(path)) { + return replyBadRequest(F("PATH FILE EXISTS")); + } + + String src = server.arg("src"); + if (src.isEmpty()) { + // No source specified: creation + DBG_OUTPUT_PORT.println(String("handleFileCreate: ") + path); + if (path.endsWith("/")) { + // Create a folder + path.remove(path.length() - 1); + if (!fileSystem->mkdir(path)) { + return replyServerError(F("MKDIR FAILED")); + } + } else { + // Create a file + File file = fileSystem->open(path, "w"); + if (file) { + file.write((const char *)0); + file.close(); + } else { + return replyServerError(F("CREATE FAILED")); + } + } + if (path.lastIndexOf('/') > -1) { + path = path.substring(0, path.lastIndexOf('/')); + } + replyOKWithMsg(path); + } else { + // Source specified: rename + if (src == "/") { + return replyBadRequest("BAD SRC"); + } + if (!fileSystem->exists(src)) { + return replyBadRequest(F("SRC FILE NOT FOUND")); + } + + DBG_OUTPUT_PORT.println(String("handleFileCreate: ") + path + " from " + src); + + if (path.endsWith("/")) { + path.remove(path.length() - 1); + } + if (src.endsWith("/")) { + src.remove(src.length() - 1); + } + if (!fileSystem->rename(src, path)) { + return replyServerError(F("RENAME FAILED")); + } + replyOKWithMsg(lastExistingParent(src)); + } +} + + +/* + Delete the file or folder designed by the given path. + If it's a file, delete it. + If it's a folder, delete all nested contents first then the folder itself + + IMPORTANT NOTE: using recursion is generally not recommended on embedded devices and can lead to crashes (stack overflow errors). + This use is just for demonstration purpose, and FSBrowser might crash in case of deeply nested filesystems. + Please don't do this on a production system. +*/ +void deleteRecursive(String path) { + File file = fileSystem->open(path, "r"); + bool isDir = file.isDirectory(); + file.close(); + + // If it's a plain file, delete it + if (!isDir) { + fileSystem->remove(path); + return; + } + + // Otherwise delete its contents first + Dir dir = fileSystem->openDir(path); + + while (dir.next()) { + deleteRecursive(path + '/' + dir.fileName()); + } + + // Then delete the folder itself + fileSystem->rmdir(path); +} + + +/* + Handle a file deletion request + Operation | req.responseText + ---------------+-------------------------------------------------------------- + Delete file | parent of deleted file, or remaining ancestor + Delete folder | parent of deleted folder, or remaining ancestor +*/ +void handleFileDelete() { + if (!fsOK) { + return replyServerError(FPSTR(FS_INIT_ERROR)); + } + + String path = server.arg(0); + if (path.isEmpty() || path == "/") { + return replyBadRequest("BAD PATH"); + } + + DBG_OUTPUT_PORT.println(String("handleFileDelete: ") + path); + if (!fileSystem->exists(path)) { + return replyNotFound(FPSTR(FILE_NOT_FOUND)); + } + deleteRecursive(path); + + replyOKWithMsg(lastExistingParent(path)); +} + +/* + Handle a file upload request +*/ +void handleFileUpload() { + if (!fsOK) { + return replyServerError(FPSTR(FS_INIT_ERROR)); + } + if (server.uri() != "/edit") { + return; + } + HTTPUpload& upload = server.upload(); + if (upload.status == UPLOAD_FILE_START) { + String filename = upload.filename; + // Make sure paths always start with "/" + if (!filename.startsWith("/")) { + filename = "/" + filename; + } + DBG_OUTPUT_PORT.println(String("handleFileUpload Name: ") + filename); + uploadFile = fileSystem->open(filename, "w"); + if (!uploadFile) { + return replyServerError(F("CREATE FAILED")); + } + DBG_OUTPUT_PORT.println(String("Upload: START, filename: ") + filename); + } else if (upload.status == UPLOAD_FILE_WRITE) { + if (uploadFile) { + size_t bytesWritten = uploadFile.write(upload.buf, upload.currentSize); + if (bytesWritten != upload.currentSize) { + return replyServerError(F("WRITE FAILED")); + } + } + DBG_OUTPUT_PORT.println(String("Upload: WRITE, Bytes: ") + upload.currentSize); + } else if (upload.status == UPLOAD_FILE_END) { + if (uploadFile) { + uploadFile.close(); + } + DBG_OUTPUT_PORT.println(String("Upload: END, Size: ") + upload.totalSize); + } +} + + +/* + The "Not Found" handler catches all URI not explicitely declared in code + First try to find and return the requested file from the filesystem, + and if it fails, return a 404 page with debug information +*/ +void handleNotFound() { + if (!fsOK) { + return replyServerError(FPSTR(FS_INIT_ERROR)); + } + + String uri = ESP8266WebServer::urlDecode(server.uri()); // required to read paths with blanks + + if (handleFileRead(uri)) { + return; + } + + // Dump debug data + String message; + message.reserve(100); + message = F("Error: File not found\n\nURI: "); + message += uri; + message += F("\nMethod: "); + message += (server.method() == HTTP_GET) ? "GET" : "POST"; + message += F("\nArguments: "); + message += server.args(); + message += '\n'; + for (uint8_t i = 0; i < server.args(); i++) { + message += F(" NAME:"); + message += server.argName(i); + message += F("\n VALUE:"); + message += server.arg(i); + message += '\n'; + } + message += "path="; + message += server.arg("path"); + message += '\n'; + DBG_OUTPUT_PORT.print(message); + + return replyNotFound(message); +} + +/* + This specific handler returns the index.htm (or a gzipped version) from the /edit folder. + If the file is not present but the flag INCLUDE_FALLBACK_INDEX_HTM has been set, falls back to the version + embedded in the program code. + Otherwise, fails with a 404 page with debug information +*/ +void handleGetEdit() { + if (handleFileRead(F("/edit/index.htm"))) { + return; + } + +#ifdef INCLUDE_FALLBACK_INDEX_HTM + server.sendHeader(F("Content-Encoding"), "gzip"); + server.send(200, "text/html", index_htm_gz, index_htm_gz_len); +#else + replyNotFound(FPSTR(FILE_NOT_FOUND)); +#endif + +} + +void setup(void) { + //////////////////////////////// + // SERIAL INIT + DBG_OUTPUT_PORT.begin(115200); + DBG_OUTPUT_PORT.setDebugOutput(true); + DBG_OUTPUT_PORT.print('\n'); + + //////////////////////////////// + // FILESYSTEM INIT + + fileSystemConfig.setAutoFormat(false); + fileSystem->setConfig(fileSystemConfig); + fsOK = fileSystem->begin(); + DBG_OUTPUT_PORT.println(fsOK ? F("Filesystem initialized.") : F("Filesystem init failed!")); + +#ifdef USE_SPIFFS + // Debug: dump on console contents of filessytem with no filter and check filenames validity + Dir dir = fileSystem->openDir(""); + DBG_OUTPUT_PORT.println(F("List of files at root of filesystem:")); + while (dir.next()) { + String error = checkForUnsupportedPath(dir.fileName()); + String fileInfo = dir.fileName() + (dir.isDirectory() ? " [DIR]" : String(" (") + dir.fileSize() + "b)"); + DBG_OUTPUT_PORT.println(error + fileInfo); + if (error.length() > 0) { + unsupportedFiles += error + fileInfo + '\n'; + } + } + DBG_OUTPUT_PORT.println(); + + // Keep the "unsupportedFiles" variable to show it, but clean it up + unsupportedFiles.replace("\n", "
"); + unsupportedFiles = unsupportedFiles.substring(0, unsupportedFiles.length() - 5); +#endif + + //////////////////////////////// + // WI-FI INIT DBG_OUTPUT_PORT.printf("Connecting to %s\n", ssid); - if (String(WiFi.SSID()) != String(ssid)) { - WiFi.mode(WIFI_STA); - WiFi.begin(ssid, password); - } - + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + // Wait for connection while (WiFi.status() != WL_CONNECTED) { delay(500); DBG_OUTPUT_PORT.print("."); } DBG_OUTPUT_PORT.println(""); - DBG_OUTPUT_PORT.print("Connected! IP address: "); + DBG_OUTPUT_PORT.print(F("Connected! IP address: ")); DBG_OUTPUT_PORT.println(WiFi.localIP()); - MDNS.begin(host); - DBG_OUTPUT_PORT.print("Open http://"); - DBG_OUTPUT_PORT.print(host); - DBG_OUTPUT_PORT.println(".local/edit to see the file browser"); + //////////////////////////////// + // MDNS INIT + if (MDNS.begin(host)) { + MDNS.addService("http", "tcp", 80); + DBG_OUTPUT_PORT.print(F("Open http://")); + DBG_OUTPUT_PORT.print(host); + DBG_OUTPUT_PORT.println(F(".local/edit to open the FileSystem Browser")); + } + //////////////////////////////// + // WEB SERVER INIT - //SERVER INIT - //list directory + // Filesystem status + server.on("/status", HTTP_GET, handleStatus); + + // List directory server.on("/list", HTTP_GET, handleFileList); - //load editor - server.on("/edit", HTTP_GET, []() { - if (!handleFileRead("/edit.htm")) { - server.send(404, "text/plain", "FileNotFound"); - } - }); - //create file - server.on("/edit", HTTP_PUT, handleFileCreate); - //delete file - server.on("/edit", HTTP_DELETE, handleFileDelete); - //first callback is called after the request has ended with all parsed arguments - //second callback handles file uploads at that location - server.on("/edit", HTTP_POST, []() { - server.send(200, "text/plain", ""); - }, handleFileUpload); - //called when the url is not defined here - //use it to load content from SPIFFS - server.onNotFound([]() { - if (!handleFileRead(server.uri())) { - server.send(404, "text/plain", "FileNotFound"); - } - }); + // Load editor + server.on("/edit", HTTP_GET, handleGetEdit); - //get heap status, analog input value and all GPIO statuses in one json call - server.on("/all", HTTP_GET, []() { - String json('{'); - json += "\"heap\":" + String(ESP.getFreeHeap()); - json += ", \"analog\":" + String(analogRead(A0)); - json += ", \"gpio\":" + String((uint32_t)(((GPI | GPO) & 0xFFFF) | ((GP16I & 0x01) << 16))); - json += "}"; - server.send(200, "text/json", json); - json.clear(); - }); + // Create file + server.on("/edit", HTTP_PUT, handleFileCreate); + + // Delete file + server.on("/edit", HTTP_DELETE, handleFileDelete); + + // Upload file + // - first callback is called after the request has ended with all parsed arguments + // - second callback handles file upload at that location + server.on("/edit", HTTP_POST, replyOK, handleFileUpload); + + // Default handler for all URIs not defined above + // Use it to read files from filesystem + server.onNotFound(handleNotFound); + + // Start server server.begin(); DBG_OUTPUT_PORT.println("HTTP server started"); - } + void loop(void) { server.handleClient(); MDNS.update(); diff --git a/libraries/ESP8266WebServer/examples/FSBrowser/data/edit.htm.gz b/libraries/ESP8266WebServer/examples/FSBrowser/data/edit.htm.gz deleted file mode 100644 index 69ce414f47f4b25a70160fc4985b2bd320c9b90a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4116 zcmV+v5bN(BiwFocSqfGF17&1sbS`LgZ2+BFd3T~p7XSZ!3LZT(!l)>itu-X0#w?vZ zYPz${oB~Qf*#a0%EWi8QTYxd?e(%kh&S|La?!UUXEOviCxaoA}qV+$8eIYcmJ~3bgZT;t(sGHS0oMQ0u@XwhbcHEBL2u z$gNQrq}9ZIqfJ!Zs55Atr}ivPF9k(qIoW>>4YydUc9@DQ-7HJz`ED#EtT|T3f5Kl z8`UlJe>drq;Tee=MALNfb11`zm~Q%t_QSFT`Vy#OpLhcLT)3sNMFLW3$1d?I{dccd z{qU_ig*Nm(YNnZ!Ar8jGO@E&FX<*G=+(}LrK$W`(HRhIQMpcb4O+%kUZe_5*QM2YbO`8@26Wh-SO%-w@!Vi zef6zl%Q5|7`}+IM%bh>8``=aPUc2qze?OeiAIIPB2Qwfz2>ZjE&TYrO@0h*0;azl& z+D2P5DYXYTo$1NxrR};-|NdfNb~n$4$8FcS_;ypD9G!L#%)4)QTL+EW_V&4T=>~(B zx3=A#?Yt%f=j0`*-G<|n*K_!PbyYU{YBIb&xz&=dn^EWB;KettYQ8lYr8|u@y0fSE z&zs+z+9f4-PWkoib;lVTzg&1^esN*G`DKrMy&QNXR)k?$qsXWuM%K-G+U#`lR{#HE#u=m z+dE-gHKz8+ox_G6xq+KfK~0v+e-(Tt^mP;ypC-cp$Y(K%L;pRCEYonUv-~oRypVvI zzsMX}K8^XdTIJPzn`yW$*SEE{@xFDdd@bhtUsMXr2W#o>OA0Ri1L0gqo*7mOWQkhk znYu6p)-AtKUI{B-q8OqG8FJZUryOjJpVDx_joCKXzVwcO4RULyJmgS^FwCO(hMpS*ful%gdHKl726h6xb0S zz(uP4yH?5OWqC1kTn`AVRXKiuxBR3}9?U1ku-%&I4=eR(p`HK>pO#OpHPwr$(&OW- zTw1nFU0t@`IfHy;Do1*>QX-%;;fC71c_aaqQ7WUrGH+H&c1f;qidHT%T1uscpjnbt z8C7g~u_-vMO~KW-lvolqD#|TaQQ?%MQW08OsL$Ya1pThq+EQL5OI8xem4RlwMl)7U zb+O@8)ol^loW6lAB;-RHv}$V{#>iokb8vyfMERyambCP-^uB05u78)+R1($XK`cYREIu^_Yz^hs*$+Z5<>wX%Kw&LgQ8zQOVDX9oT)w4rVJ+ml=jC6j(G@F5@lb!NaHd=#_571U z5Vywo7ZRR1$DDz31U*BqN&C&mM@4%0xAIh$<^8|alpgSSP@iDhglXlUGR*VowYhES zO)!7g^;da8baRPaBkOKU1_%??NR$nD8Di^9$j2IHHg+Mlh)&AjrLwW)PQ%%n$Qb%h z{UJeH&U}J%&?}SW8EfH9~xMvo;~(LB_1UkmiMh+y%HHKOW?vT~qYf;qQh%g>uT z*hHl6YOewii;tx;DLwGIrHwVt9KN-_+H!T~5zuWH9lP5XOGz~*V*ylHB8E#b1M z?zBXqw)a><%Ni1?7-*T9+p-}dDiTaFn}U@Ms%WkWgO&^tQYVrD*~tQh#NGK`W4>FP z?};s|K%a+*G;rmX@>Nk)F#H0I?(WrP963QOEiroP^9N4a^b;rW%U|-@6h@zv9Kr9G zg5yiZaa3@GX#5pNFx&pb(Z+3-bDU)y?G?xIU+V~do=CLv$F~^BJ4#ryeS*Q^r*eZ@Xm+GzA25`Ork%JeJg*yrW&~Ho!wYDf$E5Df)l` zvqIz+ipk^d>cDKsQ!+HZBkLc<#-|{8*M=$t?>)&byE1RPyH57rlbrHc#+85M5;ISc z-w=Bh6MeD{Gz(I{BuRwtU3p&=n?x?-W;Nv2D%x`qqui+L2CU~Xx8AcKAQYG=>Zt^k zIm09LX7L*nxapC)f!^XCnJ_QmG2&|WT|g&9hE{{JLt&z#u>KDBs_r8s^TT z4#n`2l$ShXRe1@nz{)!>3S9_=&XCa~cUfNirFuG^12PO~Z;IBIP_GQrhZ$L>05&rn z?us^+)q9ktb@w$$)nx;4#l@9yHv1(RL zr3U;csBS1fU_ZYf=Ekcajz1md#w&=85n|~2tzwUlb1;pI!;@7Y$yyzrrh?X&9*n2T zLM2UeDI>JTAJ0%@h_Qvx(zZV+vpP?_SN+cifgSX~IK$mU(RQRUKXD4YE%KwTb}Pv_ z{yu7~ZRd&r>IhnkS7in?SgcJ7H$5KU|H5cj}dg^PnkNr1nx3Rsf^qM?f zfZsh~touu* z;X2bpEJVP0Gs}G=owRaTw?4r3&peMju+mH*oQ+7zP7I4B2VUb@X~aq+Q5vaKd)WWS zQyF%?4lh}U#y_6^Dc2g)N<;FqvxfOVo^)fGl_W?e2&#&ys1zQJ7kFr9R#M@BD>i0g z1$GPkkQ22?gtS9JjZ$Sxt7uxiQeuo1zbP4eAu+Q;bBY%m%pEb4B$OGU^~yf}oBQ5z znnsLezZIDoQknqf}imTiwGFbZ(Et?a^C=VpB;v=e?U@_`bA^7OFF4Y7T(pbBjSoljyf=s`J);D`6P|@L8g~)8pvIXC-oLu+57;=eSqhFG zCk*vAlLT?hQpbe>1kOchf!FaeomtfD8Vtgep5SMj%Mp46Mvi&|(7%wGeGFnM-~fW) zp;OLjFlAG3O{ork;zdvOB`vXAv1)^J^ z38xOiS%7b5Ow$zbjzSNpM**9O6VAF}fN0}tCb5ZT4FfAYCjmqiH-Ap;PrcNQAjrjq zC5r-^*^m>ih69#q!3xN%WafendS|^skGa0hzV$CJ7{60v|1rU%7!Avf*#T~rAe@1x zM+9VLF5s7qEPa`>F?k``T^`GBJOS4bu*iuZeqa{=n#MPdkDT!OkY(RCSjP(uKtaU9 z(FlC`o%1w3O5#4hs>LjjO&w>P!@v)x2@S!YWSWK;fB|oMd;! zuefxW(0({d2gFD?B{BN$8^z`^aQ9F`+r;l)XOQA>xDR2tirpYR&-Cr`;9@4rV&n7J z5XffvCP82T_zyBqK`IO*z)(bNe!&g6rZyV^4)Mbt_%OwW1v?OwrMP5j zB>Ya2Ps46|U*qPe$1x!2KnXZQKCp=aA^Ogpo8I;Q-)W}4EIJ!fkf-3 zQi+x1O1-jJ%JOf!wfu@Uzp51gg}R6+O`5fuX$9j%F}-kVjXV-l1u{yFjLE#_xkge0 zDK%A5m9N-R#)-K8>Gd5lq5u{G~dX3P}9smFuw& + + + + File manager + + + + + + + +
+
+ +

Loading...
+ + + diff --git a/libraries/ESP8266WebServer/examples/FSBrowser/data/graphs.js.gz b/libraries/ESP8266WebServer/examples/FSBrowser/data/graphs.js.gz deleted file mode 100644 index 72435445a7ef86e00fe6c66b3b71c47fdbd53a93..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1971 zcmV;k2Tb@MiwFo@s$5k717~t!aAw-*8%!ep`yEM!aI@XpTRzN;M>CH`Bk93rb1y+*nfgVXr6{*O z+qWk+vpIfdbmRB&GiOD~!8-BI8jWLya*xm0`?Ns*u7@$U5b~-2FAtyY?VsHiJWiul z$g}Ci$m17Tz>sgbNBG!p6`3AIdmW5d-8e)$jYf;JJn?*ncK^L=>v4>q7o#|V#AXxo zd(ilSPVW5&A*9`%(ECk!Y+arKfe)*P?pEpRp~jzoF096?>r=Aoz97T=5)|h&-xQIu zg>l28ew8E08a6CBp`}c_5jC)_GP?DY`3f}-jd!TP&)0@aTI6>ySBJ(C^6Vjg_FCi? z4W)y8K|{N+7xu_b?5Q2tp*_aW9rTMaq?DSsrVu+rd~J}hE1ki{8Q_4DpSuf+7N=y0 z&lbmI*d0+cIw7C&8TouX>b8vuC7%rP5n-@XYJN3AXB2Qkjq~PBh|f&&!8{IOB&X(2 zH9yyb7aQ|uSdryo%OvNrl!(4VCh<(%jlKg>o+p(VLEZu+SU49}D`X?;9hV2J6#b@5xoQzr_H2o5@^1L}hMUELzT^@ZjjKOo0;;PSc-R=y16 zZ?GMY#vgK8*szEP4+cZTEn{%f2k_E z27Gde+C~f)Fg^jqZfsd{i2;6w@jVpRk!@);Z(!Q^0V_`j7DaAW9TdZ@cIt4a4szZaa^^aU29qDr;ABm46G?-=na2}Ferp=!isDL5 zZsRR-8*h`__!TcbYL6zj@e8?){|UK0{(#)d@5!y&&_(q^Zq+tjEH`wq+|b2RliLz5 zFqaQCU2wY8dG2R6eWA(3ua&TB6J>0#%pV>3Z zdyofc0!I`ut`SP$kiJ3WHi#-0<433_` z)YOv0bAAFn?dFnNRNv(ebZf$J9m~5X4;HB}|KgQLUj9wtTU0ot`o1rAx!kOPE}Fw8 zS~Q^pvJt_F1Mf4=n%)ASw`5Ik|GTcX!Dzk1YQ^WUvs*ob65M#*8Pl}n#sj~2z;Kp3 z#wt>!wBemdQe1%Fa@<6G*Tw3BO6(?)=)j$C-mo1S$t$oENe=yhLb-k<8_;yI4OzB} zRnJ39Vd(ZadQJ$E_Fo6+T+OyM?w0=_W4@|s1;9o}-pE(zujhQpQ&}rb!wtIwq}*=X z4FSKo$AySPF)Nk{6YIBX;rZfke?Eikq=c+ENp6^52>r$^5|&2ANW^K%_#LYXv7c8e z=vGa#hyxfcmO0B{UVkpiG+<$zvOo;WqJY`6)K6l6B8Kp~K2ezpCd|@8%tl_2EiSSo zFA^|v1uq>WUSe{a+%Cnisy&!<^nU5?i#QLL2@C+6O z9mvZ6hkhC(4R=nr5iF#GH6&3&)`F6DcbF}Sh6I^7IhzV|9hOnAbbP~0)_aTc?G-T zUOp0`CmZu63}>tmJ`@(Gu)SWMGnfEAsi4IU)bR<5*jr~_#!;-Z^EjQ<&VItC4B8MT z8B`^L%rDim_gH$+=1MruU~n)H8CPxfm(bCv7eR<84hDk()HUc0oPGxaFZ!1Rx^{U0 z&VK*U?e~RBheyXbhhXsb4|_d`A?>3*L0l+t&uz4z32}iM0zT**`+0@&e*in8G#?BP F000|7$0Ps% diff --git a/libraries/ESP8266WebServer/examples/FSBrowser/data/index.htm b/libraries/ESP8266WebServer/examples/FSBrowser/data/index.htm index 4e1dc7d9b..55fe5a66c 100644 --- a/libraries/ESP8266WebServer/examples/FSBrowser/data/index.htm +++ b/libraries/ESP8266WebServer/examples/FSBrowser/data/index.htm @@ -1,97 +1,22 @@ - - ESP Monitor - - - -
- - - - -
-
-
-
+ +

ESP8266 Pin Functions

+ - \ No newline at end of file + diff --git a/libraries/ESP8266WebServer/examples/FSBrowser/data/pins.png b/libraries/ESP8266WebServer/examples/FSBrowser/data/pins.png new file mode 100644 index 0000000000000000000000000000000000000000..f988bf45e3eff7380cf899affb33f7bb17e23cd5 GIT binary patch literal 55578 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 literal 0 HcmV?d00001 diff --git a/libraries/ESP8266WebServer/examples/FSBrowser/extras/feathericons.png b/libraries/ESP8266WebServer/examples/FSBrowser/extras/feathericons.png new file mode 100644 index 0000000000000000000000000000000000000000..5bb2cf63dcdc6f9c0a5309c5823652117e5bdd73 GIT binary patch literal 1558 zcmeAS@N?(olHy`uVBq!ia0vp^0zkZggBeH~J(p1fQY`6?zK#qG8~eHcB(gFvFf#=B zgt!95^-OK_&1{V<9W3l!tsOnAoje>I96UWey}iACeSQ7?{at;6JpDs`f+K>0g2KYW zf+FG~A|hg9V&mfC;^X5(qZ7ko5+mbMlQMFXGxD;5ASWj`H#Z*$N-7IVD{C5CYn$3y zT3XtGpsTO5cVhpP8PjIZn>lml+_`fXFI%}}`KomrH?7~WdBf&yo3?D-vUS^*ZQFP5 z+_?t`_U%7(o;%RxOwaD-Mjbi-+%Dn!PBSDo<4j2?AeQ#FJHcY z|Nhgb&)>d%`~Lm=j~_pN|Ni~&-@pI={{uZZ3PwW!R|qJ6jdWmOU}Pu>@(Tv0mXQn? zg1@t?0>g>3z$3Dlfr0NJ2s7@OnEe(gC{^MbQ4*Y=R#Ki=l*$m0n3-3i=jR%tP-d)W zs%L2E{@KYK7>re(E{-7{-fyqm3_4^W!j{l+X-B?C_Uj;rgIXHvc!NYkT%P{Dudb%< zJ^Narmh$7)KjqJ7tJt2Lli9c|Xu*5 z)J$8z=fSk=_)!O^{ZVhfKTu%ZcVB$9P;`aKfufcwskTKI6M14icxLK;@+r+&srtHb zHH*Q)>L)Y%`^$^%^iFxJAFFwDZ%?S)q|Y16ZEw~2oI9SH`ccHn^qrBFjKqhkhVAoA z@5t|cliYK9yU@C8ADLDg-kY$2N3@^8J4|Qi^TQ{2&YU@LhUd(m>;+$ht2DpedRzP6 Q4V2J5UHx3vIVCg!0JbYzga7~l literal 0 HcmV?d00001 diff --git a/libraries/ESP8266WebServer/examples/FSBrowser/extras/index.htm.gz b/libraries/ESP8266WebServer/examples/FSBrowser/extras/index.htm.gz new file mode 100644 index 0000000000000000000000000000000000000000..34797b0589617ca9bfd7e2dec3cabe824efe45c0 GIT binary patch literal 6261 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 fM1-xRarv_#hz^ctf_C~R#J!_`xuIwdI7 index_htm.h +echo '// This file is an embeddable version of the gzipped index.htm file.' >> index_htm.h +echo '// To update it, please rerun the `reduce_index.sh` script located in the `extras` subfolder' >> index_htm.h +echo '// then recompile the sketch after each change to the `index.html` file.' >> index_htm.h +xxd -i index.htm.gz >> index_htm.h +if [ $? -ne 0 ] +then + echo "Error creating include file from index.htm.gz" + exit -1 +fi + +echo Reduce complete. + diff --git a/libraries/ESP8266WebServer/examples/FSBrowser/readme.md b/libraries/ESP8266WebServer/examples/FSBrowser/readme.md new file mode 100644 index 000000000..c3ebb82a3 --- /dev/null +++ b/libraries/ESP8266WebServer/examples/FSBrowser/readme.md @@ -0,0 +1,138 @@ +# FSBrowser readme + +## What is this sketch about ? + +This example is a FileSystem Browser for the ESP8266 using http requests and a html/javascript frontend, +working for all of SPIFFS, LittleFS and SDFS. +This unified version is based on the previous examples named FSWebServer, FSBrowser and SDWebServer, Copyright (c) 2015 Hristo Gochkov. All rights reserved. + +## How to use it ? +1. Uncomment one of the `#define USE_xxx` directives in the sketch +2. Add the credentials of your WiFi network (search for `STASSID`) +3. Compile and upload the sketch to your ESP8266 device +4. For normal use, copy the contents of the `data` folder to the filesystem. To do so: +- for SDFS, copy that contents (not the data folder itself, just its contents) to the root of a FAT/FAT32-formated SD card connected to the SPI port of the ESP8266 +- for SPIFFS or LittleFS, please follow the instructions at https://arduino-esp8266.readthedocs.io/en/latest/filesystem.html#uploading-files-to-file-system +5. Once the data and sketch have been uploaded, access the editor by pointing your browser to http://fsbrowser.local/edit + +## Options +If you need to free some space on the ESP filesystem, you can delete all the sample files at the root but also replace the `index.htm` file in the `data/edit` subfolder by the `index.htm.gz` file from the `extras` folder. That compressed version is not suited for learning or debugging, but will bring the total FS usage under 7KB. +If you want to use the browser on a an existing filesystem or don't want to perform step 4 above, you have two possibilities : +- either upload the `index.htm` file to the filesystem by opening a command shell in the `data` folder and running the following cURL command: +`curl -F file=@edit/index.htm;filename=/edit/index.htm fsbrowser.local/edit` +- or embed a version of the html page in the source code itself by uncommenting the following line in the sketch and rebuilding: +`#define INCLUDE_FALLBACK_INDEX_HTM` +That embedded version is functionally equivalent and will be returned if no `/edit/index.htm` or `/edit/index.htm.gz` file can be found on the filesystem, at the expense of a higher binary size. + +If you use the gzipped or `INCLUDE_FALLBACK_INDEX_HTM` options, please remember to rerun the `reduce_index.sh` script located in the `extras` subfolder and recompile the sketch after each change to the `index.html` file. + +## Dependency +The html page uses the [Ace.js](https://ace.c9.io/) (v1.4.9 at the time of writing) text editor which is loaded from a CDN. Consequently, internet access from your web browser is required for the FSBrowser editing feature to work as-is. + +If your browser has no web access (e.g. if you are connected to the ESP8266 as an access-point), you can copy the `ace.js` file to the `edit` subfolder of the ESP filesystem, along with optional plugins etc. according to your needs. A typical set might be: +``` +ace.js +ext-keybinding_menu.js +ext-searchbox.js +mode-html.js +worker-html.js +worker-css.js +worker-javascript.js +mode-xml.js +worker-xml.js +mode-json.js +worker-json.js +``` +(see https://github.com/ajaxorg/ace-builds for a full list). + +If `ace.js` cannot be found on the ESP filesystem either, the page will default to a plain text viewer, with a warning message. + +## Notes +- See https://arduino-esp8266.readthedocs.io/en/latest/filesystem.html for more information on FileSystems supported by the ESP8266. +- For SDFS, if your card's CS pin is not connected to the default pin (4), uncomment the `fileSystemConfig.setCSPin(chipSelectPin);` line, specifying the GPIO the CS pin is connected to +- `index.htm` is the default index returned if your URL does not end with a filename (works on subfolders as well) +- Filesystem limitations apply. For example, FAT16 is limited to 8.3 filenames - see https://en.wikipedia.org/wiki/8.3_filename - SPIFFS and LittleFS also have limitations, please see https://arduino-esp8266.readthedocs.io/en/latest/filesystem.html#spiffs-file-system-limitations +- Directories are supported on SDFS and LittleFS. On SPIFFS, all files are at the root, although their names may contain the "/" character. +- The convention here is that the root of the filesystem is "/". On SPIFFS, paths not started with a slash are not supported +- For creation, the convention is that a path ending with a "/" means create a folder, while without a "/" we create a file. Having an extension or not does not matter. + +## Changelog since original FSBrowser + +### Fixes to work on LittleFS based on SDFS +- #define logic to select FS +- switched from SD to SDFS +- begin() does not support parameters > removed SS and added optional config +- LittleFS.open() second parametsr is mandatory > specified "r" where needed +- 'FILE_WRITE' was not declared in this scope > replaced by "w" + +### UI/usability improvements +- Never format filesystem, just return "FS INIT ERROR" when FS cannot be mounted +- Tree panel width is now proportional (20%) to see long names on big screens +- Added icons for files, and indented them to the same level as folders +- Changed file/folder icon set to use a lighter and more neutral one, and added specific "text" and "image" icons for formats recognized as such +- Items are now sorted (folders first, then plain files, each in alphabetic order) +- Added file size after each file name +- Added FS status information at the top right +- Made clear that an async operation is in progress by dimming screen and showing operation status +- Filled filename box in header with the name of the last clicked file +- Selecting a file for upload defaults to putting it in the same folder as the last clicked file +- Removed limitation to 8.3 lowercase filenames +- Support Filenames without extension, Dirnames with extension +- Improved recursive refresh of parts of the tree (e.g. refresh folder upon file delete, show last folder upon creating nested file) +- Added Save/Discard/Help buttons to ACE editor, discard confirmation on leave, and refresh tree and status upon save +- Removed "Upload" from context menu (which didn't work anyway) +- Added "Rename/Move" feature to context menu +- Sketch can be used on a preexisting filesystem by embedding the index.htm file in the program + +## TODO (maybe) +- ? How can we query the fatType of the SDFS (FAT16 or FAT32) to limit filenames to 8.3 on FAT16 ? +- ? Add a visible root node "/" (with no delete option) + add the FS type next to it, like LittleFS +- ? move "Mkdir" and "MkFile" to context menu, with prompt like for Rename/Move +- ? implement drag/drop for move + make "rename" only a local rename operation (no move) +- ? Optionally present SPIFFS as a hierarchical FS too +- ? Optionally mount several filesystems at the same time (SPIFFS + SDFS or LittleFS + SDFS) + +## Test suite +These tests are a checklist of operations to verify the FSBrowser behaviour. +### On SPIFFS +#### 8.3 filenames +- At root : MkFile '/1.txt' / List / Edit / Download / Delete / Upload '/1.png' / View image / Delete image +- In subdir : MkFile '/dir/2.txt' / List / Edit / Download / Delete / Upload '/dir/2.png' / View image +- Create nested file '/a/b.txt' and delete it +- Attempt creation of unsupported filenames +#### Long filenames +- At root : MkFile '/My text file 1.txt' / List / Edit / Download / Delete / Upload '/My image file 1.png' / View image / Delete image +- In subdir : MkFile '/My Directory/My text 2.txt' / List / Edit / Download / Delete / Upload '/My Directory/My image 2.png' / View image +- Create nested file '/My folder/My test file.txt' and delete it + +### On LittleFS +#### 8.3 filenames +- At root : MkFile '/1.txt' / List / Edit / Download / Delete / Upload '/1.png' / View image / Delete image / Mkdir '/dir' +- In subdir : MkFile '/dir/2.txt' / List / Edit / Download / Delete / Upload '/dir/2.png' / View image / Mkdir '/dir/sub' +- Delete root folder '/dir' +- Create nested file '/a/b.txt' and delete file 'b.txt' +#### Long filenames +- At root : MkFile '/My text file 1.txt' / List / Edit / Download / Delete / Upload '/My image file 1.png' / View image / Delete image / Mkdir '/My Directory' +- In subdir : MkFile '/My Directory/My text file 2.txt' / List / Edit / Download / Delete / Upload '/My Directory/My image file 2.png' / View image / Mkdir '/My Directory/My Subdirectory' +- Delete root folder '/My Directory' +- Create nested file '/My folder/My test file.txt' and delete file 'My test file.txt' + +### On SDFS +#### 8.3 filenames +- At root : MkFile '/1.txt' / List / Edit / Download / Delete / Upload '/1.png' / View image / Delete image / Mkdir '/dir' +- In subdir : MkFile '/dir/2.txt' / List / Edit / Download / Delete / Upload '/dir/2.png' / View image / Mkdir '/dir/sub' +- Delete root folder '/dir' +- Create nested file '/a/b.txt' and delete file 'b.txt', then delete '/a' +#### Long filenames +- At root : MkFile '/My text file 1.txt' / List / Edit / Download / Delete / Upload '/My image file 1.png' / View image / Delete image / Mkdir '/My Directory' +- In subdir : MkFile '/My Directory/My text file 2.txt' / List / Edit / Download / Delete / Upload '/My Directory/My image file 2.png' / View image / Mkdir '/My Directory/My Subdirectory' +- Delete root folder '/My Directory' +- Create nested file '/My folder/My test file.txt' and delete file 'My test file.txt' + +## Credits +- Original version of FSBrowser written by me-no-dev, contributions over time by various contributors +- Icons are from https://feathericons.com/ . The resulting PNG is passed first through https://compresspng.com/ before being converted to base64 using https://www.base64-image.de/ +- The spinner is based on https://github.com/jlong/css-spinners +- Minifiying of index.htm is done using the command line version of https://kangax.github.io/html-minifier/ +- Idea of embedding webpage in code borrowed from https://github.com/me-no-dev/ESPAsyncWebServer + diff --git a/libraries/ESP8266WebServer/examples/SDWebServer/SDWebServer.ino b/libraries/ESP8266WebServer/examples/SDWebServer/SDWebServer.ino deleted file mode 100644 index 1679b5806..000000000 --- a/libraries/ESP8266WebServer/examples/SDWebServer/SDWebServer.ino +++ /dev/null @@ -1,320 +0,0 @@ -/* - SDWebServer - Example WebServer with SD Card backend for esp8266 - - Copyright (c) 2015 Hristo Gochkov. All rights reserved. - This file is part of the ESP8266WebServer library for Arduino environment. - - 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 - - Have a FAT Formatted SD Card connected to the SPI port of the ESP8266 - The web root is the SD Card root folder - File extensions with more than 3 charecters are not supported by the SD Library - File Names longer than 8 charecters will be truncated by the SD library, so keep filenames shorter - index.htm is the default index (works on subfolders as well) - - upload the contents of SdRoot to the root of the SDcard and access the editor by going to http://esp8266sd.local/edit - -*/ -#include -#include -#include -#include -#include -#include - -#define DBG_OUTPUT_PORT Serial - -#ifndef STASSID -#define STASSID "your-ssid" -#define STAPSK "your-password" -#endif - -const char* ssid = STASSID; -const char* password = STAPSK; -const char* host = "esp8266sd"; - -ESP8266WebServer server(80); - -static bool hasSD = false; -File uploadFile; - - -void returnOK() { - server.send(200, "text/plain", ""); -} - -void returnFail(String msg) { - server.send(500, "text/plain", msg + "\r\n"); -} - -bool loadFromSdCard(String path) { - String dataType = "text/plain"; - if (path.endsWith("/")) { - path += "index.htm"; - } - - if (path.endsWith(".src")) { - path = path.substring(0, path.lastIndexOf(".")); - } else if (path.endsWith(".htm")) { - dataType = "text/html"; - } else if (path.endsWith(".css")) { - dataType = "text/css"; - } else if (path.endsWith(".js")) { - dataType = "application/javascript"; - } else if (path.endsWith(".png")) { - dataType = "image/png"; - } else if (path.endsWith(".gif")) { - dataType = "image/gif"; - } else if (path.endsWith(".jpg")) { - dataType = "image/jpeg"; - } else if (path.endsWith(".ico")) { - dataType = "image/x-icon"; - } else if (path.endsWith(".xml")) { - dataType = "text/xml"; - } else if (path.endsWith(".pdf")) { - dataType = "application/pdf"; - } else if (path.endsWith(".zip")) { - dataType = "application/zip"; - } - - File dataFile = SD.open(path.c_str()); - if (dataFile.isDirectory()) { - path += "/index.htm"; - dataType = "text/html"; - dataFile = SD.open(path.c_str()); - } - - if (!dataFile) { - return false; - } - - if (server.hasArg("download")) { - dataType = "application/octet-stream"; - } - - if (server.streamFile(dataFile, dataType) != dataFile.size()) { - DBG_OUTPUT_PORT.println("Sent less data than expected!"); - } - - dataFile.close(); - return true; -} - -void handleFileUpload() { - if (server.uri() != "/edit") { - return; - } - HTTPUpload& upload = server.upload(); - if (upload.status == UPLOAD_FILE_START) { - if (SD.exists((char *)upload.filename.c_str())) { - SD.remove((char *)upload.filename.c_str()); - } - uploadFile = SD.open(upload.filename.c_str(), FILE_WRITE); - DBG_OUTPUT_PORT.print("Upload: START, filename: "); DBG_OUTPUT_PORT.println(upload.filename); - } else if (upload.status == UPLOAD_FILE_WRITE) { - if (uploadFile) { - uploadFile.write(upload.buf, upload.currentSize); - } - DBG_OUTPUT_PORT.print("Upload: WRITE, Bytes: "); DBG_OUTPUT_PORT.println(upload.currentSize); - } else if (upload.status == UPLOAD_FILE_END) { - if (uploadFile) { - uploadFile.close(); - } - DBG_OUTPUT_PORT.print("Upload: END, Size: "); DBG_OUTPUT_PORT.println(upload.totalSize); - } -} - -void deleteRecursive(String path) { - File file = SD.open((char *)path.c_str()); - if (!file.isDirectory()) { - file.close(); - SD.remove((char *)path.c_str()); - return; - } - - file.rewindDirectory(); - while (true) { - File entry = file.openNextFile(); - if (!entry) { - break; - } - String entryPath = path + "/" + entry.name(); - if (entry.isDirectory()) { - entry.close(); - deleteRecursive(entryPath); - } else { - entry.close(); - SD.remove((char *)entryPath.c_str()); - } - yield(); - } - - SD.rmdir((char *)path.c_str()); - file.close(); -} - -void handleDelete() { - if (server.args() == 0) { - return returnFail("BAD ARGS"); - } - String path = server.arg(0); - if (path == "/" || !SD.exists((char *)path.c_str())) { - returnFail("BAD PATH"); - return; - } - deleteRecursive(path); - returnOK(); -} - -void handleCreate() { - if (server.args() == 0) { - return returnFail("BAD ARGS"); - } - String path = server.arg(0); - if (path == "/" || SD.exists((char *)path.c_str())) { - returnFail("BAD PATH"); - return; - } - - if (path.indexOf('.') > 0) { - File file = SD.open((char *)path.c_str(), FILE_WRITE); - if (file) { - file.write((const char *)0); - file.close(); - } - } else { - SD.mkdir((char *)path.c_str()); - } - returnOK(); -} - -void printDirectory() { - if (!server.hasArg("dir")) { - return returnFail("BAD ARGS"); - } - String path = server.arg("dir"); - if (path != "/" && !SD.exists((char *)path.c_str())) { - return returnFail("BAD PATH"); - } - File dir = SD.open((char *)path.c_str()); - path.clear(); - if (!dir.isDirectory()) { - dir.close(); - return returnFail("NOT DIR"); - } - dir.rewindDirectory(); - server.setContentLength(CONTENT_LENGTH_UNKNOWN); - server.send(200, "text/json", ""); - WiFiClient client = server.client(); - - server.sendContent("["); - for (int cnt = 0; true; ++cnt) { - File entry = dir.openNextFile(); - if (!entry) { - break; - } - - String output; - if (cnt > 0) { - output = ','; - } - - output += "{\"type\":\""; - output += (entry.isDirectory()) ? "dir" : "file"; - output += "\",\"name\":\""; - output += entry.name(); - output += "\""; - output += "}"; - server.sendContent(output); - entry.close(); - } - server.sendContent("]"); - server.sendContent(""); // Terminate the HTTP chunked transmission with a 0-length chunk - dir.close(); -} - -void handleNotFound() { - if (hasSD && loadFromSdCard(server.uri())) { - return; - } - String message = "SDCARD Not Detected\n\n"; - message += "URI: "; - message += server.uri(); - message += "\nMethod: "; - message += (server.method() == HTTP_GET) ? "GET" : "POST"; - message += "\nArguments: "; - message += server.args(); - message += "\n"; - for (uint8_t i = 0; i < server.args(); i++) { - message += " NAME:" + server.argName(i) + "\n VALUE:" + server.arg(i) + "\n"; - } - server.send(404, "text/plain", message); - DBG_OUTPUT_PORT.print(message); -} - -void setup(void) { - DBG_OUTPUT_PORT.begin(115200); - DBG_OUTPUT_PORT.setDebugOutput(true); - DBG_OUTPUT_PORT.print("\n"); - WiFi.mode(WIFI_STA); - WiFi.begin(ssid, password); - DBG_OUTPUT_PORT.print("Connecting to "); - DBG_OUTPUT_PORT.println(ssid); - - // Wait for connection - uint8_t i = 0; - while (WiFi.status() != WL_CONNECTED && i++ < 20) {//wait 10 seconds - delay(500); - } - if (i == 21) { - DBG_OUTPUT_PORT.print("Could not connect to"); - DBG_OUTPUT_PORT.println(ssid); - while (1) { - delay(500); - } - } - DBG_OUTPUT_PORT.print("Connected! IP address: "); - DBG_OUTPUT_PORT.println(WiFi.localIP()); - - if (MDNS.begin(host)) { - MDNS.addService("http", "tcp", 80); - DBG_OUTPUT_PORT.println("MDNS responder started"); - DBG_OUTPUT_PORT.print("You can now connect to http://"); - DBG_OUTPUT_PORT.print(host); - DBG_OUTPUT_PORT.println(".local"); - } - - - server.on("/list", HTTP_GET, printDirectory); - server.on("/edit", HTTP_DELETE, handleDelete); - server.on("/edit", HTTP_PUT, handleCreate); - server.on("/edit", HTTP_POST, []() { - returnOK(); - }, handleFileUpload); - server.onNotFound(handleNotFound); - - server.begin(); - DBG_OUTPUT_PORT.println("HTTP server started"); - - if (SD.begin(SS)) { - DBG_OUTPUT_PORT.println("SD Card initialized."); - hasSD = true; - } -} - -void loop(void) { - server.handleClient(); - MDNS.update(); -} diff --git a/libraries/ESP8266WebServer/examples/SDWebServer/SdRoot/edit/index.htm b/libraries/ESP8266WebServer/examples/SDWebServer/SdRoot/edit/index.htm deleted file mode 100644 index f535601e7..000000000 --- a/libraries/ESP8266WebServer/examples/SDWebServer/SdRoot/edit/index.htm +++ /dev/null @@ -1,674 +0,0 @@ - - - - SD Editor - - - - - -

-
-
- - - - diff --git a/libraries/ESP8266WebServer/examples/SDWebServer/SdRoot/index.htm b/libraries/ESP8266WebServer/examples/SDWebServer/SdRoot/index.htm deleted file mode 100644 index 55fe5a66c..000000000 --- a/libraries/ESP8266WebServer/examples/SDWebServer/SdRoot/index.htm +++ /dev/null @@ -1,22 +0,0 @@ - - - - - ESP Index - - - - -

ESP8266 Pin Functions

- - - diff --git a/libraries/ESP8266WebServer/examples/SDWebServer/SdRoot/pins.png b/libraries/ESP8266WebServer/examples/SDWebServer/SdRoot/pins.png deleted file mode 100644 index ac7fc0f9cb64d99f9f33b877798c61d02086d00b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177869 zcmcG#W0WY(k~Z4AZQJhNZQHhO+qP}nwr$(CciVQ~{mz_kX1+V;*Inz@udH|im9e6- zGGjdzE+-=j1BnR<0000ZE+(V^005*00Psr#9Q3aQXzVir006SaTu@L>Tu=~S&cW8i z+{zdLKrB2t1x!iN9Cft!MaLtb_z}}X)FTr@UcnJZ8 zAGyje3It(z0AH`42R|@^0T#6`uK`YN;5sL}(e#Fs^(OVF{pR?`n|7BS4PXMPg)a_r z5+H757CGeAgRY>!5dC%c7jPIKD!$*?aTxH_-X7m=E4>e1wa8)2x>9X#?ME%aWHg#R zfM0-o%wYtZIe9LBV>A)G383FmUWbfJiCzSydOb$H4i~qRAyOtICa#6}1 z5IE-tA-VBXsn1A4MrQq?(is|1M=cHlomzI2aLQ*BI$-BNB~mxcFq^>&k zrZA5TfS?CJ`PcxE1EB40;w5vOWIyY~UlW&_mNc$cKt`6+f8aPJjiG{?(gGlrJ(#}w zx!?cz7Jv-ysBtY0uw_=Z##kzx3z~<^JwAczm>~lS&}0h0l<6=uP=x`*_~Z{lj1P0< z$1n%L(FdQFe|_o4Xa}~_hX?FW2M?3-3$`PK2_JY}k3|7{-C08zH|Kbb=pv{IVE= zLWB~*lNi*3{}%syJi-waXlTA6Iyz$%;|Ls5%)TB2T_t@c{hu^UDWVb_M=(Y|jN#8d zg?bA$q$;G8;OGHTgU0&cH3ci?XR2q4XIvL>&X~b{WIIqc&n!F|%+x4N|IMh)NXx$F z0}s2vHhM1H+9=lk&b`1}D=*R>_+BtyXkUokNW1}G(m03=V9EeEf{;G4DxxIB7(^OG z4+vTaBq59f-1%^Hp(8}caLw@0FzTVrA%X*7@f0II2ZG@E1aV7o<06j*h9{`Vu#V6uF@!>Lj8hl?lNKFpA^~S?AIbImU9$vgReq zCDWx@1*hMRzf+J^Euxizl?s(=mo&`_nsS&FJ<^?WoXQ`~&o&o`7=$pqF=R1XrYzDS z(;XSS84Meo)Fs#P*O}I*8$^w5$9f9(&EJ`9F;i9eIK?^zu!@MBa5~7c1$AY4g?c6M z5(dXH#_Ep}{zUwVnRFhHo>)AxJ|a4DJfb|J9ebg~hkS>m3RViPhAc(Cru0%)QZ=F& zp|nw`QoU6EEGnuvszI(*Dq6~Ol0BiHfM7AWXJO_NGu(7E$`6 zl&#EOAzV?agje2gF=9brLAeB5;am}2ZdvwG0k!l}rYny(FE?kmDz>s#ZZB`BmRNIE zF}}D}?k@hoESS@8RKzfWeibE8%}mWpUBB|Hnx-1U8tBUI%6S83L)KZ01C|4VgVTA# zNyM4i8SS)Ysd~}7NOZ|@PI6(hdNuz=e3y?`!8gz+@tYEe1So|+hd;X?Nw6Y6Q$IvM zd0(3-D?U&@VqT}v#&B(Ig>AfT;I#$9%D{AhaouvkFoM~c=})Fg`g6McRGNvpiL(jN zG32rT@x@f;)b7k;HX5VtM^}tHFO?Ow0yV5US4)xlyJk(JRlS$3vDI*WMO}xbbn|n) zj^Vb|*UZQe>5fV6G>~S;72qaJi*?KH)#X)?%ahCCbH{W2bMmt{tOIO6Y$i?(rvVN( z_9|PB>*s;dNz$3q#2KwA;|cOf;Y{r@)>Le+ui1BAWDC^hA3w=n%0cFpC$v=`R%+H3 z&E!o-R~*+G&*l%w5(LI{_o%m(!2!Wj`<27Q(!>*rSryu$$<@lS%E`=QoNMjtuPM<@ zviZ^()1}oF*I?U9ycOC$+fH3P$?ReM9575UNFYtrKB!N$ zOSN00{2hVsff9W9A;=AcN2WD=Hlo*6;Wpv^ND9<$@Oo%r=f(fjWF4Gop_S?FZqW872)j&mGEO>*3msfLW2s!5mZmwon|+)@T-IY zl9yCWs%do(!}N)UC1bP{Nm^4{UYyA2=qPT?k7%5s8iUy3`U8qX5%!#{?JQO;*K3(; zyDmvD@y|)%)S!eQ)o_|{PQ=i}tptfg5x1N!hn-HN-lMPaT!jFIm?EhXkrI|7gaw1e znLG1OWFOBC5f2%U*ghdS^3z$niM3>U3TSwhNc?#PdB+lLj$5Y)m|@;yVmKfX;$Bs&-TlcacT(7j+PDU?N*r9=-#Vum)0w0k_HlYl0Z(>OS7%< zHT9;4!$~G0BO{tx&uDKsQ z?_cL1kuSbq!7r16Ks{iXm5-GYa0jp^I9~ST2XdqOp@YrK6wAZQyRo;ikE=mTO{*KL zE4a#>9nG*cNUJb-nhsSTZL4?39*yrnFpx0AWPK2K;j3hZvOByeb`@7mgJ(B7X9oSlIVn7fo+?kB^EWVGE?;P%U7#Gfp*+4^V;Ei!p{KF7f*`bI zv=X`&JyqVuPv@U?vvw~R!?mG1;5$-L$lt#|O#f&OwwiuB{8TzJSvZ|$-)6^Jh(BR#PcC?ox<6yg(Sc4}c!AFOZR3xH7L*R3m99=0^M zl87h@e3U~Pr(bn7$=fVEiJIp0BOz;kq4?R+5tx(20M@VDZd(V30&|CZPiBi}1c2&8 z#)mg0y5Fa++J4%bp3w!VaNk7Vzbd0Zm_c`|r~YnZv(1&%oYbVHI1FvAY4nY34UB2r ztnL2pYykkc+&KOYt&N@Z@!hPgY#cewt*fgmjVmLKt%E5oJv%!)Egb_b z0|WJ64QfYs8z+4?Y8ywQ|8(+y{0JF48akNUIhotq;Q!-S-@w+{iJOq{pFsb0{pWKU zyP5xYBpb(nP3!LjY5zfJ>1pU_|I7DZQ?7rc9CGGv##U-V=GMkGj(=nDuraf6{fGYl zLjF7A|FBg5Z%am||I6}!Apf%DqWxzA|6@Y`nXUhj{!JGTBp2;}rJe_J5q7^C0Duob zT!>%E?boF?gqHH~%TGg~Uzr~kI3#(S-nD1QM?yjRH?#0!QxuisTXDLHaj|*1F~%QR z#Y8dRgOFs;1oMPW0Z2&lNY2X3F0jwptv}tPhuqK0COo+>*WHe$><`&jS?P=?n81bYj=oNu|;852o&}FafgK?hLg%p6neS zDlQgrJ-zMrcw?2yWFI(6!YnySNI=43v-zFm`r^8{xG?kaU(G7y%l$0|03972I~SLl zFW^-JNQ@t!&sTeYUV2v6+TkQr(yQD3!Mf(=;V$bCu8cvvF2pcEJD0C^IGgHCG<%!%1LR+zDH`G6qQ zmF>m>>k;jUQiK^g3$wPCyfYj;DQml>vgCTOwD|e|x)Ch~sRG0kk9^q%K3j*pw8d=EJnlp4)}l9{69)bFu!Q@l3{3Z zGyWZVD(nZP_WwETgtaOs*>A{kI9!2zU});hzM5dlDXHZ*@y`sSeE zXc#30Z5s*&L`48Y%B9v;>Pz1r76t{zcsL|Ajuyc}k(Bc$iVqn)oaF3Sif-(pZC^hw zl@^ofGhLVW&p$7+Kg<{Q&<6DNx4(XykFhq01e!!IJ9b4RRJ6$xUfgMK zKZLQ=%A>4bh&Oi7r8cS(593oSLNIEYs%a<kADUM~&{bT)j=YUhf+sSBN?R~> zo1(JFyP4oqInp2`l_(kxA|O%zg)})*=jcqsXhV`%+R2NauHK);!>Mne*D!H|=!nhL++oUkYY z;KWgUps26)KzVOAy-81N2mlF5f-{h)4qm96htnN5_|5*+FdBugtJH=&xHR=d(wQPW z8xR5>9G_j(b-|;iBgXcYwNr1E(C%lICpg?IeES!VDA@$7 z1EHPC9e9>%ayMGe?q2B1jIyo{48??N5{p^bHaWqHet@Ti9sE8TUOY7tq zGKc)&%4e^n3QW^V8!6?3{(ZQ*#T!#*5}e~f({atZ#h-O_=e3BfKO%dp>V;6t-5l+% z`<{Eu{;P5y%G28W_2nFNtK_2Z?$~Mc@_l*d=W0Z}9m;p|PP?AV?vGZgZu8}IxWR|0 zvy6Z-hphTjOZ~#`zpsoaUaPoOlEsD*R+%m#z~zk@^-o~Zv}i(Hm4c}TrR%?b z-M+svaX4G)gaA*XHNC#CrjkBo+LGA97vMu-&L(ak1c;tUf>huNbW$_{gyym&T-Xv;GrStYca;WQb2u)1> zKwTMXP(c+C6Gd_x%8OgyC@lF!)n6I_c@%a8iZ+btQuLiGkjucDqNK` zBR8%dbBDg5z>i#K+7M}Ru!yKlt)|IhpjJwo_Nw+qd0=_Y z`DA`ZSu6kam>V{fELwyOLOoue-tKx=Njb&TOQnBNJxlpr=N(g%hp?aMl0m^S-H)VQ z%0*h}RCQ7)7WT&AJz{l^Z>`z`=Bm>>+B#REYfl&)k2PSSk}yRvFVJPa22WtiTe9i$ zIApZ+8UI?I#Y*Ejs8iKfO^nlw{iSyAc%3&sDIUg95sT>=J4oe9OF!!cyfx+$e$w6s zswT@Us%7>$w0LI(%inwR-~f!DW!Uk$BgRI>Zn3ycw6zq?nDKfZ!6XC2W|gS z%dJ?PxX0v2TZCpLZ@)%3Y%Gi|=5#YwZ{31UYYS`9QzM((6&NnjL4VhfDT8G~WUE{^ z-sBrPOVj9JGl<`*R`g6j`Yz-w9Okd5N|EOfA?sH?drZ?hBA*wWE3K7*JSdc6_S zR;F;j(G+~p037|DExe%G-?QXQZ@_9rO=Z^WLz_J*I!5~t6_Z`4<*t66rBRTmFCNK$ zL_3?%Cu-PdPvLx7#YKdJBwIcBH`X&0`)YD;MmbR<=9%o9_^O>sS(*;|`^<-#RR+)n z*oS>m6ODso!qVbgs-8 z!ble~<^xBe&xo;*R?jacr{K!fNT!`}<8l6h;=nKi!=7WVU)m4yco}5x{fn@52QJktYP+wD3?xt-o7|@pDkpkGRk_G(QWmQK`ZBBV7HJx;3TCPU%Qcqu^K4HBs zD3FMC6`?4YQTsPFfjHC28#NAerR$|jhKt}hX^RTd3cv+3pZ-C}EO6|3#y=eH-WKGU*?Ho1i@}l-SjD>sDVV>?(T5ALJ&0^Yy&W6gH%TZ%Z9z8|vyW00! zL6nl5L}|xIoJ%d8?U9(o<_Mv7Us~wJQrkn`#2e3MwF=dXlbPbbk4r`HKZoRr$&hYV z+JLseV)=k9PCAZ#RHL1s6xHt)@6w76`gMB=pYzjV2pK`%NU7~K;cm)WyV_&nksKh` zkEi8j-I3;57v{Ds88jrj-1RBI1}(2k&%bXcbiuQZ1v|6s-4ZH-vbifO4_*hciM4-l zT4^HMuvpDNPHY6{lq_ngXt$kGs=t}dXFSBbDA+k~+R^EpgXnxbni@w%N>J>s0TUWY zFR&G#mg}=xY&g!S@0{uTMdPfuVV_%M;lEaQhj=&o%=?6z?2tc-vqa2m?oTx`>c%9J zpgN^Vv>*_uHw<5|vCu6nNKse>O+DNtSla1J?O7xr@oT}_$jU|CyBUNLYlc!`JfsA9 zPoCJMZ-{EP34TS2Tu?Y-v^^|h=L@3E48ZljwCGNkKp4fTs3_6(=LigN)>H<1g)mJB`U!!)_8{^ zWiD?{8i4aoDB`4SXYcx8F`APNWsq({NqUwC8SQKDnV$AM62Cvs!Sg_h*2b4^Ep3WP zlK7LR!sI*6%r2p(IDP*L99!1bJNSoi`l4Uc6N!}eD%C)C^#qZzlay6 z2$A{&SHy$BWNe_X1;i70GGKTswU&1e2dBm#33_AjS*7ok=IBG;oL6&YB&I| zwIUR<5W0%_iil*Kq6~@2ba-r>s6g##I4>9HoLQg_hp(guK74NpT5J8HtgdQ)Ul1jd zKWpPM3Z?mVmsb49xs=(;% z1dpm(*~^Q;azBvSDHtyrSnfu{hA@suq-dy|V~R{e+yYhN(v(8WM4J?e!V zQN-;yM4vb^G}5G&UtKYzz|pNwr?d4=tpT5?#Q+oh&|<}kv6Cx9Itxv#1vRj2?IQp% ztHWI>-e#~~d?^0Otro{3&ErXY8f}fWxW1Al(U+Ar&JZo_Enf?NY@&zB=>wHQKcRH( z)yLqZ>jmxo(UyYYNDyD7_-f1?Exvr76vi!Zm7FzN6>S$*)PjQTU&n}Ghu}iV2w!Pf zwL+%HEiPSDQ7|zvP2}@}M=SxAGp(_(C3!u+4+4I>sI_-L2w{HB{5ymdi?;(Q=><;z z+xWhuEA=m=0|b{El~wOomkq8q-f+^Sf-XZ5 z`ajH3A21&)5o?o9#VyzsqJ4xcoXlWvP5X(muRP(9iLXVdWv0VI{s(V^qF}oMeaD9m zECWxMCVE37a&3!*b>AKs3MKEWQ+);EQ2X=*X0R+;IFUO<-Fs^;0A4Q(@r364L01Fj z9S7%aAl6K$rHO8>TWndoa!I~ivR)0V84p9eDb|bwa*1^ZH;lKp8Q?bFVC;$5LpYz< zrY>I5y)V`SZ#Z~|6C~mi35cENy})EW+7WrhL}q9b*hc$*f|Q2kXW*cP?YZKPkm`OtPv-jpTA(lBt!}LrP9$U!L=?w*DrzQ z(|VL7SP8OOQpHc{%qd9EJ`qW+yQ@kPy*|9X5F1%-fTNoQhn?Sg| zUkBXGB;FG02e*%9ww~oK-91yI1(R3k=HfxL1~Ue|{a7L%IHKI@Hr8 z+OZ_J={MW0o!^u-So@UtInalVyq}*8V9CGOzukXnES`>#yJWl|wV1%3V*~SdV-zZme2FJ;wQuZ#WIFB%1vzuFW za~%A*J)KjT=zZ3;D2XAQYXf~ps-G`X)`MWAWQnE2`Sw{@A4%O?8YnTLh+I1SG%Xu# zq-J!1aD*Td$+1M#^7C>+pkx5G@#wUSQzPLe2+!1t_f0uXps8~cYS}tmToqvu;}9jCk=FQ1kYM-c zv>SA>HD?5>QHba8^ZW%tloOI<*;9TD(cbvViQny3!x4HGE>W;dBjr-Qef4p&jRO4T z+HBDmcmo>oE$MXHGoEnDl!ar|!dr+K_;oLn-uiYrsHG|HN-zU^3Pxm-Wm;UIEdZXs z&0%zonZWH~H1KrBMZ)~L8zEVP%C@AaJ8Y9vcz?n$h4=UpW;n8OMB(W0mFSdq?F_w= zX$@LL=uBHYP0gv#YM`{Ssb<^zj%F%L?6ZL7CQaAN-8@fm{^;&L)lGjt^>kTr=S#6W z!KRFN)+`bx=z+VoAH~f20q<^J=_?r%FJf84pcBWxw-4lbhiwg*M&;*|~+(kFC z8ZD{(o4LH@njR~#i#Rv!i|v9HuJZ)eZ?|n6Jld@YL#czdC*~otIB_C>oL2!$pKDak zHeM9&`!394Y9+p2rk3G}%Uw>PG$~qWA#`aA)5VGoHoGItiW@4`&c-)?88B{+{!Q6- z*`U!VQlJL z!|H?C%`(^sF*%F9IZ03Li63dkicCb@F63+%1I$Q2QJDU2#3Ju}L9__E;V1%n?*r3; zKrNxc>S$O|qfNtNQj~Ux6sa--7Wp>kqGds%3Qg|WDRknN6~CVWA`etk{ai9f8rW=e%6;* z>1s@sM!^qZ*TyqU8X^Fxh`ZebU_qexQ(tcWJSKpH6(Q+xcVmk-j<}OvYLLHanEUdHG;X#W}?VLjCAe^f04R>Qsfn2MB@PI5Sdp z{4wTW;7&*)Y)f`#XI_Zp1vqBS{Y3AnaQ%>2pFX*nxR80(;pALUgY z79t}@2KuuXl%`tT6LWF+vzLSb?oV@^to)s_s6H? z)+s|p{VD!CJSZh-k>2vdV2~5g!d;OYL*7FxDR4wQqc8ymsKQv#Y6aq~SEZ#cj1~l( z?aYH*zTbX%>_ZdU%rs{UKIUXa)KUz11e0t06K0fzggfw2+G@?O?)O^;03tHkWc_rT1QWAO)lgitdo%fR&{9x0$S(=KQh89 zS#3xueWT)}ksc&2FOsWsJiqt=^*PE&Znq z6P;5 zE|@AI@2Qc*T3CaQK1^C&KLgcJWbmFQm3E|X;QisklX|_5iu&q=v^{GaGhL<{T-nq| zQ)gN)bv%Hx`}7WoF`x)0`uzbC28M_tk{1*Bdg=o$S9+LsXV6o*C-H*G1|rfs&V&4H znCV1sV;`@{VRJdX9g7iar7JMO)E+J>;{zhHqGb6JMyfH2qU~UzxJ?@Q4sdDJQ1j_Y z{zl55_d9jXoCbc@+r|C-Qlkx%UQ#+%v2odyLqKUKW+d@GrL3=z6>i52hsnQf#7;|J z>8)2g3Rg!T?BBkg!gvZ$^VDP_((tXaBonjt%vvqi)Q0f#f92;K6+8cqdm!PLb0Yk5 zpc6c!(-XM$z=kyGxhBSj4cTsID(3y`272QBnC>1#E6At8hmk1Wk<@KW9Zn?}hn9w6BHUL{U~bfU%Tbno(6~aU&~G zU4)8SBi22*s9dP4;yOK#QB_~WQ-w?Kgn-|}JogmwsimbA(c*bOOQy_rd|9>ZSeY(u z$Pi6aQksGGI3@Z9N#A4mU_!w^F<0epVINbzIu7OWZUzrN_erEICagk1-^*gQQO}#v zVjG}_ST`V^Nth}9XsoBelt{hV-NUGs0jR=GUwgWiL?$a?Kc*P1ol=*AZZjRCz%iw0 z|KlpJhCj)>W+xP_bX#y8mL(kZc=~KHE79p0bjqi(go1q))I&3m|CY5)hSf=KUX?;P zH*9Z|G^Wuzn6S&Nw>r^Af%T)|WEyx4;k^^IpG zaJWUuMjsqR?1A&MX)A`grlaOYk;6L`a>7~a?~P?o?zij;d{=pSA}VSKYY-(dx2R1U z;8P58PA#m5;7cU@e8Mp$HIH0HEY zU*AQp!H^xGL0@dHHY+nmlyhy&>y2#rF?m?h(mP|C*`iaxTaJsp?g`26K2lfmAje{2 zF6NP!62VNg>S_id`EguVlB=^HAzWuCxT$Y2HJSSjh9Dn)0EBZ8xz-@WadQx33e0$W zPo zO{rQ8D+)?m6OI&V?jEG1kq56{_|+yFy4|m$U&Nrk!niuojmo1UuZ^+6mfs_{g(c=g zTm!%egtQyw=>L-)zXi;STUaORbRY}Ea ziK6V*)LNY}%t`Vg1$xy-cOr0ZklkIJ>-6}`=#-L1JZth##T=_jWcggLoDO4~)ONsi z!b!NWWgH&_I{J9zXdt~i*&Q92jlu1MjvX1}_0=Xbbq?!fb4Ytt!pog|1XeVAq)w>f zXu6w=oa=@>jeC{CyxNqsI^BF`)RL+sMJ)vn#$jE2zPvkX_qZv{ZUs`#W}Ao?#$hO) zYgWQ*n!)QJs*%F?fbJNBdGxrZ;C|;Dyhs!tRS^Q718xN3c`yi-^H*fjJg+y=<$*^K z=*?&?=MNeBu6`}Fc(klb@H@mq(ajZ#>gY&uo}v_|%&?=;kXDk5ndail(NUJ9YjSY6 zLNJSc$%8QMPr&x3Abhts$7jo81;+5--kHS}-FSk#IL6VUkJ^R!w;k6&RYuU>T);Kk z(WT60sTGKz6HVh^%FmQ>EJa(&+S~8hE%Ny}!0?At9!pXx#Q_p%jz|>z*z=PV{lhe0 zGyGTNjm-!LLnIJ7$tF*CEE0nPoL$IPRaN z^dm2|FcEFtJYS6`_Iu3d0kr3FpEfG!Q%18BY21m&B4z2F`=L@=&de37@*pKdG1(8h z4qAu6vCrC^zK)B91lXOK(QwBLgkEpL9%vUbXW;T^_a|aY0TaI_MXate#(~q@%f6Nj z47Gyg*FQxLVkcODC5D5{4M|BYx*ILm!86_E(+lS@mrjfLy~#BqHMeP(F>`+4z_hnQ zb)Olq?VKz`9v0=5_Z-p?CVYir46`4yuUR_R!fXqF2M2x&VyW=4{dtVJRuY|CDc zIt2Q&1IGB~W>uxz#nrF&12Arn_|2N{QoxScsSB-SXBi|{2@pBOua-+T;K2pLWE;{D zZIV%HX=aF+++BRS*5xdvp8kyoX@ygUi#J32W^56=`dyq?-$HQ}imqBD%hkZ@&XF#h z++zw3=g|ij+C&1x=H7`BAeC*32iN1&1`OOi zLu0H=u|8Y@_I9)!JOfeMLGiaFjKk;k9h##fByn@t+~V+Tyru}j9~hx8+;F+!Kx2Ct z7<%FCF@CbBd7WU{1aQb`tYFsQ!ifg%jU#qA>?t#Mm!oel-g#ME@UcK+4J3sI_cT+E zt~3lP(t0A55_t``7Yup~9nSk@m*wC$ZFy_=5fkUgw2fv89bQM@dY5dtiKl9VGA2!g zW*pyl9Z}E2_o0pBa>ImoY;(h!=t7-3;`R0ds4?1{`8kG>24_iCAabt_OT5#&LzVt+ z%yaTtKMjX$#7IMvpO_|b#=Mf`wq_|!K^!#sUu9>=?lGH1%&0R>BV+6{m;xaO6URnf zk~ow4TPg~%ny_f*Bz&kfm#5eZbFs1dOLilA71><@RnPPGCv$R3qP9z5hdvy@wfYD7 z49#}T=z%EKxL?dUxu1yK;VY8?>R@DnV6(NJ!es!7$KA@I zX#SF7va`PhTo97$M84>?SW5g+=}7i2J!X*M>=yQW7Kyde7tZ6r2D3>QUfrBCQYaKV z6JI#9#c(C4P))=4#jO_u2_=4OPXuA5EJ1=mLD>RW+yl6bJ8-n z-JOf^2lp+M$KFPKk<2xuS0swvQE!zRzJn2e)&b}z*bUo#UiTK6l((DY=YpatFS1AN z)^JGueoaj7fWUiKiY1LEB`EHxjp-;5PF*imZW|j&Cd1*mnhz#5RdjXAk($apx*i?_ zL~~ct*B*Z>uW|5IbYDuz)Q}Se7XEwG5q9v}7v_yVr(3+t!TeVCk;F@vk0uIcC}jC= z=*T&gET9vON7+T#wI;ggK#oG6HIoR$0736KmSmfo ztF`1wm{o+jtKKlYJ=Q*@L2m(%52AE^)bs+M6<;xyEaEScd$L`#0xl6ZGzuWrT6d=& zeFRCI9ILBHpc;*4yFrOogGf{SJ;B04QpQ9i)Dv(u_lkr^uYxa&TB9}M8?L&G(u=38 zGNt?nIgO?n-dtN354HmRPbYz82P-fN&wJhNsSd>xCw={hT`pw`wZ{W}5e5pQisebo z{ERR^-#{3uvh8-OKx=a7Tql@tLAxq)O<6S!2~;0(Qp!-~I?PWRv}Rb>rt#laW!h{G zISt(fPGyt%(u)?VR2n`Nd%7&mhH4~jEw(OCOP)NjUv%WS#Kojq!_gSwwLqyTpBvA0$rEL9 zjuk22wYeokQYV@M25lc3P~?Dc8^g4?e2DGD#I(cQO%;Oc1-!JbeLLd>IR_1QjInVA z1vbMl)JmqpI9Lyka#JOAtdhwbQO(09e{FO0leiA47TgZzNx6G(HtStnon8RPmz|>O z%?WFu;XgG~6MVn+Z3bMjCNA#5L#YP*l^&;>7V}*tR=&%=)AkX!W0s@IPczi&oOdEP zNy_Qzue=y3O$OTUw}cz+-OYb|@6Mkel2groD^^5xC`2Q7C&ql?qU@Sj$SB9T_HcEXne@b7Zyzb9eHY2zyWewwHCi?(i;d7g=fi=8BUtE+pv*l~&2I#$*>Jq=J#gWr;Ej~hkWc2P9 z$$cX|6ql2;)?ge7n}N}cVmD$1U&Hr#P{05`Y@3FRRuQr*55!2>MV`kGpLs-asD*?? zNP%U{aIUBPt6!)viP_mw3j;7(o}O)v^UwgpMdf~?7R%6rF0&Q)4cF zg?Ok7P?zR}p3?NPtxG9#N5{W^RaIEsGNm@zq*9YH0)f1_*VQ#uZGya|%H?J!hX()x zrKxNDNJ?n2MvRYeuB29Bt*^oaT+k&)WKkW>Zz;@XyaVKFRy317BuN~Z7!R&>;YLMC zS1{jnB&hTA^DHDH>Dj2`k(wMk2VZK4mj=AMyLeDgg@>{-RdIgsq?eD^HCl&ty55`* zDWAC3AtE8oPpm`Gc0VS}h&4|?oX!`vIbWb2cim}N6J3>;o}4dNu64T7l78<7aV*p4 zyhqGa=$V<3+XQJ3XZdH*F9npS@Z|K=^bK=DPUSLosw}+NlAI3&L%m+L?z(j=2?Djb zTwy+6t_KD;ZtEKjkyKguX(bOI4pF^Yclxh-mGt%Z^Z%K(T>?KkI@0?yk&Mpl1-{X2 zS?be&GZ6)Fxcl|>MejMHq^7Q(%j@qN-$$y{pa*a11Y%E81VK(W7HsB(46S{ADl-TyM1_dAfXuy-sugi^15~ zO~2;E0OWeZd*rr62+U%=5yZOdh1A~O-W*m@nwaG02OyQs0Aen`sBdEN=!Dvkl5+k& zZRCX6gy#G6#p>~RDlb`HrQYzDSqX@Rg#`=>dN7QpTcvJr?sgsJi^uLje7)NnTOysg zlD@$8aoK3LSOSxlS3J3#-OZ-G0co@>WXd%r>VLKiajkQ>9?;8*i96xXs9FzR&F)&;00tJB-o%J0Q?OyCd z-qSS->f15YD=YvkmFu*)x<1e)JXXi@A*ht{AL1NXLG8uM@rfkq8qHx@Q%U876@!5m z5N{5|S>p9}z@@F0f13fnt!|f)neM*_Y8f5>Vu6{STj|%@mPLjXBBRhZpu#m#C5JlQ z0asxuF)Py_qR8ayPPGIk<4hDV6mt+Dwc(!0Z-UWZT|&gUWctVecdmbvY|ft=6Sj&# zgd`GVlk;sl5as=LzDh!SJrm+f&j_D8B@-K;3N5Zi*>GQ4O~ZP)d$r-vGX?Qv#JQMZ zUk~f36-rD|!L$b_>*rNXqtwz|p=2UG6P5Q5s}tIioGxwOg|4ZA*)!5~%hPWfDZ&~^ z9~l!MN>}h-d!!7&&@&M00xKTI-iF!&=Uy$P1Ux8( z*6bMI1JhKB3_mGaHU|2UbPt4 z&bBVUX(+7Qze2lxE|IGy&$;DvMz7_atO_X_^vwb?0k#A|!0oojzbA9+T)-<&YBUEqRTdkN7=;C6 z?dpD~f<%2poXf0AL%Map$|TfJ8;z1}HPpZ( zgG3IW^*Y{z)T+B{r8X12m)Dta_G-0*1uSeX=!hEWNVY9-L{guc7;ev|tb6@ntne#5 zu^rmnxsr2(RPO#p+d3k8?Q@2Hk)5Nkbj;dVz~ny1!sq@b*r1MB@}TAEd2(^}+KEk1 zogIvc6}VNY!n(e3LH-aI!QA=zB;@%iwOfJ}+|b*dIyNKl({cFb`!$0dIglrPhY7Jx za68$8{^$0lnjMDjm0C0}hA^*BzPFdoqWJmsAf(&(xxXxhIN^xcP_6dMjeZPFk~0(K zx9NLzrWxk}mrUra5Zs$28pil*?5K`a-dv}U-O(dP zj2FtqGW5u5%)pT1afAiIbXHL*pnpJ|^w@Q`fHrEaJ00gU9=Zyj%zV94p-ivBb;DqN z6ol%IE&-{ef2V0HV1FdNrM6fxvF=-V5z(G9BA-}W#))fyOWA^%GdYy2ZB`%GtlaA} zUd%3DWV^{~Y2^u|R#Z5@kx|iYr`s4@4lzfx`#uXV4gD4?bc{-4da+9?^md1yJd55 zlc^m4`RpMDZHQOk-JCEKyr{MJoYVIh+~|41_lelGjz=ObnL<%Wq8e=`Bo&}|tjJm& zIVe@Zky(7lg2G|9d9ZrD1%sdlx1)uco8%x$H0byRo;DxYnBi~F>KNlu9{8ZjR4O3U2-dz-#h-R#jCumi|y zcSFb(9j#&zhk6Hgq?)NZ7jxAuI5*heobLy>mNDR*E=Y{N;>N!VmEOq6AP1Cx!g1h5 z&xl{0M>J(1(0OTbUxJlh@&2yQa%c!_CwNrgs)cBycrE z*1*R>?i$?OVLA7`^qI89Z+>s);HnqTT|u%Tb#Ez@36fcSq0A#M3I<$ql$;>pE~1B#8?uL`+XKvnUoB z*~YOeijQ_kZ%poI*Y7nRp}3J>j2jttf|#y1gnH@3t1 zBst*PJmCEG5=+cmO2Sp)nN4zD-S25}j=Sp@c1B;GX)NZLs8zfHvZIl}IGpY{>KRQ> z+EIU?Q1$vfbN7t)EH)|)XfWsDh{eT;tS)c97a|IwQmi2cJRM8)7n(rWaNB+f2HC0J z{+XF<>IK|t$9EZ%Y1|$5OnUAZFGzBIm*^6S_^H_(jZFhr#_JipoEumGKPDBiyP3n@ zeTP?cAOLznw+mntlob8TqqF#h_TJ9PJhWwRFimWPl5J2ZXi!gx>J}PQ$(~&$o;(L| zd`x(<*>XnC`0R2u&m}bsvw-TcHA9krW*nn0fy2%RV1%Sa zzuXuJJ9Y$2Zp01iXH=I_`n+Poxi0#>fb-|T@90Z8a@Fn7e$`6-1Aa~w7|uSYK?ey3 ztl{{y-`>M+*r3C%o^iCX%j@-zT@Vv?`stYl#ze7ZDxVmU0cDADz&S!YH@uNdK3x#* z^$zb;TQ1}ROcPEwwnr8v)v+NK_pvY#L$3a*V${k`S zz8DTbKcwAA(y25{^;rVRY2)C29+r znDAght_Hk$*98x=Q(~BycQ+AQ&%D11Iz`u4>jR6VYnOle8--oEqUk@UWrLh#b=pHa zLNY<|mwhsyURlZ?=vgsqrBAE;JAc1VvBwaN${rk%4KmLtppeFUH|zWvYFBv@dk<)H z7DwLUm`)4jr&+0R6=}(Fb5CzeMDga1c~)Go`)Cjo@2!auV9O*u&{<0td;Z=%yUe5q zJ%V_r?GDFL&B4Ehs8&jljiuL)>)r&Rp@8Ais9(m6Z*YKw%g+EWDXITe#O}o<&D?#q z1Aw%98vD3yy(i-(YJP7!KMr!n$ypd$_y4eUPCp;*I8c?V5;SVReoUfIjt^j_>m=NV zbzK04!AX>@kLlH;L6k;d{WiU=7Z}&1)-sQ^w(pujD@FfPn zV@{8JXB&=n54H0P)&VOz=pGj##T$XcX5hE^qr34(QmzVYz^7Vy5WVSIwveM((MU%H z1i>~(nE&j975PNusUU#rr|<+t{?kp?E7wOkkK-mE@NJ~q-=SaW5Y__>BpNec?+x;h z)03Y-oLus-S`G53oLw0d1a6vO=7q^0LDhYESLQr9`ZoI(!3O-Z5z+UD9T(8qvjYJg+6KUS_dJx~Dt0f2y|}?7aA7gA7e! z{REOubVW~{rE&VhhSQ<_o$!Xb@CEdqv@`4yKv$|GGd7-U80cS>wn@a*Ux6z|GI}j> z!kr(Et*e>?y+ec;5QHe(3kV??jv=#A=KDya2KRs28s`+Qh(ltN(r)|!83j3_vZN`5 zLPb-kD7k;1I=is2ge`n^`?Q^HB*M$$8pHBoXjt3%QR{Sbq&67o7nC z^x@6IXs46|qd?a}nj(wt)b_23JzR)Fr0ihklosA)!|h?_g8DZ@mLU_cYyDYB7rvI* zS*6O-;NGmO{~d$*h;jiG&3T_QO6M9!hEC+A{ZJ<{@$zpMX^;MRkwQo!>6fTc?mYUk%PEUE_M9B<6X z-6P?(+klQLxrfPo?@))(8ZaWW=xJ(K{J8bk6`Vt4@--Nef-KFdL#*giFrjaiQQ<4p z7?!4}^m*4QslBhz=lK~2{>7tQ^N|i*Q-|Z(;$sL*W5YUIb}2BO;$1ha2}z5^z)thG zmN(ySnt@(&jPUkTLf4l9Zl0h^RpWElsXGvzLg&FEeadFP9~Aenp^VWCf!$@{O3I14 zNNO!lCSHmIVXt8AGZw|;*W+&z<#;#niw*G;IrkfZwdi*3`-GX;UGpn2vt}vBm1vgP zI2!XeH=XD_wPd8FkXq%W6y&NeeO~*6CAEvfV>(dLrQLoCWaF zv3iN-`fV@dT@%JdwHaw-nHPw)Q7HChi8s%Jbd=8jtFcLi$5@F@`j$Mn?^A1QzlH=B zb;6bYJ#8k3FuRq=2HsCBjdtYpHeHTw`5SDZ6-lN80X0(FhwdHYAlfx3!26-S6j~YHR*PXTQgl2|Ex%C^cq?>Y-%6`%ZzaHr-&) z@6-*{DlM+K@JSiGGvtFmm_DaQ7)1HdO%+^iw%&!IZ?ZNw`*EI9ws~vAcSoD@l7XvI zhwb%_HEG?EK#rd4`k#>ysC>uR#!EpJf>`N)!PDd=k^}*+uTsf#3;Ph>>506CdC)(` zdD?-v#)3dX{+PEu_S;hJjTP~3G`h=&L5xcxwAw32S&|$os!K4J%}i@+_3@ks)(HMs zhONxc-$K4+Wb*a33SrmDIXVwaz)6tlsxpKP&FkBQJ|p9kQ|iD2H}%bQBSHFl^t6jn zr3tHo+Vr(~ab+(euz=DUb5DaRlG4rIP}rLsC-^xZ{Sid!nhQnM&&t*Hm*KgO^Jao~ zRK-J<)M&Gf9vRL~G^>w@s!!x_y#R&LtB~R^aAv>?AK^uE!TPsN8|;$bFPDp~P-RQ_ zI$@nbGtNK;?e}#z}uHB|zC} zdi%@96?*0P|D=V8EJi#k41E2DT>aNW{pyW6p6(Jq3kHpe_xT(Lc6mFfbi7MWEwy?1 za%W!6nwmZ^my&7!>GO{JBR_m0PX_ONy&J)odxKo(^>_ zuFqjjv5X{878g8*w?1cx`v=bXUbD_Y;`5|y&b88=no0{9TG-7sK{8#7>{NPZ7}8_B z_Le}rKF^F;*zVDyVtK9o*$cL*SuP{D8GafBIKwlbWyxkFl|J=*VgX~A`r~}Ny|{*S z0McGq|Cso*uL4ay+c=Qf6fTMf0{^>BJ~10jX+qvfSJ zeN&^SNj@jQu6G@9c9`=RT;}bt8*adrfl`h4+HirWtiV3+I6K)3nBZrLbXYr3C03dF zk%{7wgsC-q2;j?%9`*;dT!L+~G#29MdPSkQ0zRDDB&uhSoYV zu*!}ruteMVE$e7sWL5zyRk*jOtEs{i4LUqwLF%=&vx(-mkhI_<#f|Eh3|9(>U!Hx1 zC=5G@LPiz_1VKjZSsch(;9zGWQ@TMm_IU1SH|6FM-2vQ$E4@iwMK}l+fuy?OSJRgt zw)(7%aL^eZy`xi*qP9WjHm(wTdRV0)+8%#Dd(N=b&b!lFS7JXsz$t^A6lqP>YSfIw zjAWNzQsiGTtW$Sq$~?hg-qDH7pcYIon*Q2UrxG+uf?HfOV4!_@{rSN#lJ%xkx8#W8 z6Y?Rmq{1`w3un><_b0kiNt(eh+)eNX#I=2~jast=w5Q<4PW6H7U5uGl!(CElh)tx}?G?0E_Z|9;*DKm~B!~&EmB2}< zWa`fsRlVCAa$!CMc9Qw3^E<&8t{&LjM_NI zmiMv0%a6dOW6QK*8uVrc)`wlAb-p5P-*!Y##8b3+kwPwnUiK$6UTSK5(f2OPgKxRu zV=*N%)T}^!zN~n%yT0N2eBQ&ov!cRS327E4M#EQ);$fjx&SOMp$&Mu|cTwo#(`8XN ze*MGYAZnST9e1QRLQOq!Lpu0@ZCJq;{_gUW2aRiPWuS^fo05jZ80gIjqS5A~LbQt3 z*6(b`xnh+Oyxy}r48qA#jCN59>gKItpRShbAQTydxLrWzvJ$yOJAC&})OK}k& zT(tPysi;O1cR}8SD!KzcP;v9mGo3y;0tjZVKz+%H%$rqfAgO~X9Hu}1FpfizQfsz= zql3Kpm?G7Ri0I$DLG4eq!w&>(xD8#m3W_I#m>?<f4l|Zd$cpzhhilj>Nf;-;x~;cpwJ7V@l33RU#RU>)FTLJMfzFcu?97wP z$1j6!c?bVK5)rVmKZ@3kaFbP&#dD2z#0~nk5oX}1CE?TGa*F*sSe=fqt+Nw>WevKw z5NjGIg5ryUkhA@8yWqP$cxY<=k<8h#t7b~whXN@W*gM`N-lYCUU_zbaClgsbn!~&5 zaV{i{HgDpCGBkzj_@X0xz8S36?G{n@d)N_vyWDPpr9`oy_-Oa3HbWLj?VKT z)XBKV!5p^;MWfYOe>8wR`6-XsP2RCq`88J>j?6#(qwCacdljZh6~*A|$d2lhoJZ*% z`dw}JjA9yxn7f!+79e?W+S}fCY!arh@IhE-4?S!fJy@=LH2PHUQqN`LiX@aw`ct@~ zSTqWEcYhx0Am~KWJY*BCG_1(1bF2BE^Dr#F-cBKr%_Z|t0o;t<`l21mP$HU#esXPP z1couSQ|~%cKt&EE{8p94Rp45`d_i-ag-PU-fH%UfH)!E@q#&XPJh;lL|MY7qoT)N=F8KOA{?vo z)Eq~2i7hY*LF>!kL(uR~>`rg}cI)!duozu1Bt9rc@!d%5@Ky%3dk$%|$D~O4%O$fu zR`i`**dy?U>tR6;kAcJ6%7EPgT&NVh(yg)@^0aNmqmfTz(?cGZ2<1wB_Ov#8hY+65 zQ(rXbZS)$1oGX{O_B9#GK%lJzrrQmOg}td@&x0kOLA?*bHP5pXRR(=MO&ZBBhLXa;k@u=$n4U8OF6&h+%hqY36%5c~Pdd?y4lf1SDxg7yJ=-%Ap;SLDQ?{ssM)w0RRr`y5Sg{$WS;ugS7_Z^;*i? ztk6d%M9C-`y<+!}azKi41>e^_Fs(K_fE}NY>PuzwmCZ}1!}*Hj`f`v-XS{g|Umf>< z;xh$`T#pDsUujC%t(}`zM&ZHaq`CJf2Zv*cY8@^#yl;m{=om||1cbWjf!H9`Un@k|-6=x94V&=#O^Um) z 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 Nla*4EtQ9v7`9ICfW Date: Thu, 30 Apr 2020 05:36:42 +0200 Subject: [PATCH 17/29] Make getContentType available for 3rd party usage (#7254) * Refactored to make getContentType public for 3rd party use. * Added missing "jpeg" extension * Use getContentType() from mime namespace. * Also add .jpeg extension --- .../examples/FSBrowser/FSBrowser.ino | 45 +------------------ .../src/detail/RequestHandlersImpl.h | 18 ++------ .../ESP8266WebServer/src/detail/mimetable.cpp | 17 +++++++ .../ESP8266WebServer/src/detail/mimetable.h | 5 ++- 4 files changed, 26 insertions(+), 59 deletions(-) diff --git a/libraries/ESP8266WebServer/examples/FSBrowser/FSBrowser.ino b/libraries/ESP8266WebServer/examples/FSBrowser/FSBrowser.ino index caf863492..beebeb014 100644 --- a/libraries/ESP8266WebServer/examples/FSBrowser/FSBrowser.ino +++ b/libraries/ESP8266WebServer/examples/FSBrowser/FSBrowser.ino @@ -115,49 +115,6 @@ void replyServerError(String msg) { server.send(500, FPSTR(TEXT_PLAIN), msg + "\r\n"); } -String getContentType(String filename) { - if (filename.endsWith(".htm")) { - return "text/html"; - } - if (filename.endsWith(".html")) { - return "text/html"; - } - if (filename.endsWith(".css")) { - return "text/css"; - } - if (filename.endsWith(".js")) { - return "application/javascript"; - } - if (filename.endsWith(".png")) { - return "image/png"; - } - if (filename.endsWith(".gif")) { - return "image/gif"; - } - if (filename.endsWith(".jpg")) { - return "image/jpeg"; - } - if (filename.endsWith(".jpeg")) { - return "image/jpeg"; - } - if (filename.endsWith(".ico")) { - return "image/x-icon"; - } - if (filename.endsWith(".xml")) { - return "text/xml"; - } - if (filename.endsWith(".pdf")) { - return "application/x-pdf"; - } - if (filename.endsWith(".zip")) { - return "application/x-zip"; - } - if (filename.endsWith(".gz")) { - return "application/x-gzip"; - } - return FPSTR(TEXT_PLAIN); -} - #ifdef USE_SPIFFS /* Checks filename for character combinations that are not supported by FSBrowser (alhtough valid on SPIFFS). @@ -304,7 +261,7 @@ bool handleFileRead(String path) { if (server.hasArg("download")) { contentType = F("application/octet-stream"); } else { - contentType = getContentType(path); + contentType = mime::getContentType(path); } if (!fileSystem->exists(path)) { diff --git a/libraries/ESP8266WebServer/src/detail/RequestHandlersImpl.h b/libraries/ESP8266WebServer/src/detail/RequestHandlersImpl.h index 9388d4ac4..fb452af48 100644 --- a/libraries/ESP8266WebServer/src/detail/RequestHandlersImpl.h +++ b/libraries/ESP8266WebServer/src/detail/RequestHandlersImpl.h @@ -120,7 +120,7 @@ public: } DEBUGV("StaticRequestHandler::handle: path=%s, isFile=%d\r\n", path.c_str(), _isFile); - String contentType = getContentType(path); + String contentType = mime::getContentType(path); // look for gz file, only if the original specified path is not a gz. So part only works to send gzip via content encoding when a non compressed is asked for // if you point the the path to gzip you will serve the gzip as content type "application/x-gzip", not text or javascript etc... @@ -146,19 +146,9 @@ public: return true; } - static String getContentType(const String& path) { - char buff[sizeof(mimeTable[0].mimeType)]; - // Check all entries but last one for match, return if found - for (size_t i=0; i < sizeof(mimeTable)/sizeof(mimeTable[0])-1; i++) { - strcpy_P(buff, mimeTable[i].endsWith); - if (path.endsWith(buff)) { - strcpy_P(buff, mimeTable[i].mimeType); - return String(buff); - } - } - // Fall-through and just return default type - strcpy_P(buff, mimeTable[sizeof(mimeTable)/sizeof(mimeTable[0])-1].mimeType); - return String(buff); + /* Deprecated version. Please use mime::getContentType instead */ + static String getContentType(const String& path) __attribute__((deprecated)) { + return mime::getContentType(path); } protected: diff --git a/libraries/ESP8266WebServer/src/detail/mimetable.cpp b/libraries/ESP8266WebServer/src/detail/mimetable.cpp index d646857a8..c4a20cc0e 100644 --- a/libraries/ESP8266WebServer/src/detail/mimetable.cpp +++ b/libraries/ESP8266WebServer/src/detail/mimetable.cpp @@ -1,5 +1,6 @@ #include "mimetable.h" #include "pgmspace.h" +#include "WString.h" namespace mime { @@ -16,6 +17,7 @@ const Entry mimeTable[maxType] PROGMEM = { ".png", "image/png" }, { ".gif", "image/gif" }, { ".jpg", "image/jpeg" }, + { ".jpeg", "image/jpeg" }, { ".ico", "image/x-icon" }, { ".svg", "image/svg+xml" }, { ".ttf", "application/x-font-ttf" }, @@ -32,4 +34,19 @@ const Entry mimeTable[maxType] PROGMEM = { "", "application/octet-stream" } }; + String getContentType(const String& path) { + char buff[sizeof(mimeTable[0].mimeType)]; + // Check all entries but last one for match, return if found + for (size_t i=0; i < sizeof(mimeTable)/sizeof(mimeTable[0])-1; i++) { + strcpy_P(buff, mimeTable[i].endsWith); + if (path.endsWith(buff)) { + strcpy_P(buff, mimeTable[i].mimeType); + return String(buff); + } + } + // Fall-through and just return default type + strcpy_P(buff, mimeTable[sizeof(mimeTable)/sizeof(mimeTable[0])-1].mimeType); + return String(buff); + } + } diff --git a/libraries/ESP8266WebServer/src/detail/mimetable.h b/libraries/ESP8266WebServer/src/detail/mimetable.h index 191356c48..6e6a4e963 100644 --- a/libraries/ESP8266WebServer/src/detail/mimetable.h +++ b/libraries/ESP8266WebServer/src/detail/mimetable.h @@ -1,6 +1,7 @@ #ifndef __MIMETABLE_H__ #define __MIMETABLE_H__ +#include "WString.h" namespace mime { @@ -16,6 +17,7 @@ enum type png, gif, jpg, + jpeg, ico, svg, ttf, @@ -41,7 +43,8 @@ struct Entry extern const Entry mimeTable[maxType]; + +String getContentType(const String& path); } - #endif From 388d3020f223f1d3437b8ad252342534fce83752 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Thu, 30 Apr 2020 18:25:31 -0700 Subject: [PATCH 18/29] Fix minor GCC10 static analyzer warnings (#7255) Add minor NULL and double-free checks to source, identified using GCC10 pre-release static `-fanalyzer` on the coude. These are harmless to other versions. Also add explicit include of stdint to Schedule.h, because libstdc++20 will not automatically include it. Safe and no-op on earlier versions. --- cores/esp8266/Schedule.h | 1 + libraries/ESP8266WiFi/src/BearSSLHelpers.cpp | 2 ++ .../ESP8266WiFi/src/WiFiClientSecureAxTLS.cpp | 28 +++++++++++-------- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/cores/esp8266/Schedule.h b/cores/esp8266/Schedule.h index d33a7c08b..48111c33e 100644 --- a/cores/esp8266/Schedule.h +++ b/cores/esp8266/Schedule.h @@ -2,6 +2,7 @@ #define ESP_SCHEDULE_H #include +#include #define SCHEDULED_FN_MAX_COUNT 32 diff --git a/libraries/ESP8266WiFi/src/BearSSLHelpers.cpp b/libraries/ESP8266WiFi/src/BearSSLHelpers.cpp index f96254779..b3d1b60c2 100644 --- a/libraries/ESP8266WiFi/src/BearSSLHelpers.cpp +++ b/libraries/ESP8266WiFi/src/BearSSLHelpers.cpp @@ -234,6 +234,8 @@ namespace brssl { if (po) { free(po->name); free(po->data); + po->name = nullptr; + po->data = nullptr; } } diff --git a/libraries/ESP8266WiFi/src/WiFiClientSecureAxTLS.cpp b/libraries/ESP8266WiFi/src/WiFiClientSecureAxTLS.cpp index 62b7f0ab8..62969b8bd 100644 --- a/libraries/ESP8266WiFi/src/WiFiClientSecureAxTLS.cpp +++ b/libraries/ESP8266WiFi/src/WiFiClientSecureAxTLS.cpp @@ -125,6 +125,9 @@ int WiFiClientSecure::_connectSSL(const char* hostName) { if (!_ssl) { _ssl = std::make_shared(); + if (!_ssl) { + return 0; + } } _ssl->connect(_client, hostName, _timeout); @@ -170,8 +173,7 @@ size_t WiFiClientSecure::write(Stream& stream) size_t totalSent = 0; size_t countRead; size_t countSent; - if (!_ssl) - { + if (!_ssl) { return 0; } do { @@ -399,61 +401,63 @@ void WiFiClientSecure::_initSSLContext() bool WiFiClientSecure::setCACert(const uint8_t* pk, size_t size) { _initSSLContext(); - return _ssl->loadObject(SSL_OBJ_X509_CACERT, pk, size); + return _ssl ? _ssl->loadObject(SSL_OBJ_X509_CACERT, pk, size) : false; } bool WiFiClientSecure::setCertificate(const uint8_t* pk, size_t size) { _initSSLContext(); - return _ssl->loadObject(SSL_OBJ_X509_CERT, pk, size); + return _ssl ? _ssl->loadObject(SSL_OBJ_X509_CERT, pk, size) : false; } bool WiFiClientSecure::setPrivateKey(const uint8_t* pk, size_t size) { _initSSLContext(); - return _ssl->loadObject(SSL_OBJ_RSA_KEY, pk, size); + return _ssl ? _ssl->loadObject(SSL_OBJ_RSA_KEY, pk, size) : false; } bool WiFiClientSecure::setCACert_P(PGM_VOID_P pk, size_t size) { _initSSLContext(); - return _ssl->loadObject_P(SSL_OBJ_X509_CACERT, pk, size); + return _ssl ? _ssl->loadObject_P(SSL_OBJ_X509_CACERT, pk, size) : false; } bool WiFiClientSecure::setCertificate_P(PGM_VOID_P pk, size_t size) { _initSSLContext(); - return _ssl->loadObject_P(SSL_OBJ_X509_CERT, pk, size); + return _ssl ? _ssl->loadObject_P(SSL_OBJ_X509_CERT, pk, size) : false; } bool WiFiClientSecure::setPrivateKey_P(PGM_VOID_P pk, size_t size) { _initSSLContext(); - return _ssl->loadObject_P(SSL_OBJ_RSA_KEY, pk, size); + return _ssl ? _ssl->loadObject_P(SSL_OBJ_RSA_KEY, pk, size) : false; } bool WiFiClientSecure::loadCACert(Stream& stream, size_t size) { _initSSLContext(); - return _ssl->loadObject(SSL_OBJ_X509_CACERT, stream, size); + return _ssl ? _ssl->loadObject(SSL_OBJ_X509_CACERT, stream, size) : false; } bool WiFiClientSecure::loadCertificate(Stream& stream, size_t size) { _initSSLContext(); - return _ssl->loadObject(SSL_OBJ_X509_CERT, stream, size); + return _ssl ? _ssl->loadObject(SSL_OBJ_X509_CERT, stream, size) : false; } bool WiFiClientSecure::loadPrivateKey(Stream& stream, size_t size) { _initSSLContext(); - return _ssl->loadObject(SSL_OBJ_RSA_KEY, stream, size); + return _ssl ? _ssl->loadObject(SSL_OBJ_RSA_KEY, stream, size) : false; } void WiFiClientSecure::allowSelfSignedCerts() { _initSSLContext(); - _ssl->allowSelfSignedCerts(); + if (_ssl) { + _ssl->allowSelfSignedCerts(); + } } extern "C" int __ax_port_read(int fd, uint8_t* buffer, size_t count) From 9c56ed1ff90ddad5ed8e6b7c9ef3ca5f8d1aad44 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Fri, 1 May 2020 23:14:27 +0200 Subject: [PATCH 19/29] release 2.7.0 (#7259) --- cores/esp8266/TZ.h | 2 +- package.json | 2 +- platform.txt | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cores/esp8266/TZ.h b/cores/esp8266/TZ.h index ef3ed3d44..f3a490156 100644 --- a/cores/esp8266/TZ.h +++ b/cores/esp8266/TZ.h @@ -1,7 +1,7 @@ // autogenerated from https://raw.githubusercontent.com/nayarsystems/posix_tz_db/master/zones.csv // by script /tools/TZupdate.sh -// Mon Dec 16 13:08:18 UTC 2019 +// Fri May 1 20:39:05 UTC 2020 // // This database is autogenerated from IANA timezone database // https://www.iana.org/time-zones diff --git a/package.json b/package.json index 5ad2898f7..d02c4c04c 100644 --- a/package.json +++ b/package.json @@ -2,5 +2,5 @@ "name": "framework-arduinoespressif8266", "description": "Arduino Wiring-based Framework (ESP8266 Core)", "url": "https://github.com/esp8266/Arduino", - "version": "2.7.0-dev" + "version": "2.7.0" } diff --git a/platform.txt b/platform.txt index 678be1ba9..b507fbaec 100644 --- a/platform.txt +++ b/platform.txt @@ -5,8 +5,8 @@ # For more info: # https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5-3rd-party-Hardware-specification -name=ESP8266 Boards (2.7.0-dev) -version=2.7.0-dev +name=ESP8266 Boards (2.7.0) +version=2.7.0 # These will be removed by the packager script when doing a JSON release runtime.tools.xtensa-lx106-elf-gcc.path={runtime.platform.path}/tools/xtensa-lx106-elf From 4e3a4b6d210054242e5d7a13bcd3467819518cfa Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sat, 2 May 2020 01:17:04 +0200 Subject: [PATCH 20/29] Back to dev (#7260) * Back to dev * update documentation pointers --- README.md | 4 ++-- package.json | 2 +- platform.txt | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 776a7550d..8d4bc4d77 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Arduino core for ESP8266 WiFi chip # Quick links -- [Latest release documentation](https://arduino-esp8266.readthedocs.io/en/2.6.3/) +- [Latest release documentation](https://arduino-esp8266.readthedocs.io/en/2.7.0/) - [Current "git version" documentation](https://arduino-esp8266.readthedocs.io/en/latest/) - [Install git version](https://arduino-esp8266.readthedocs.io/en/latest/installing.html#using-git-version) ([sources](doc/installing.rst#using-git-version)) @@ -36,7 +36,7 @@ Starting with 1.6.4, Arduino allows installation of third-party platform package #### Latest release [![Latest release](https://img.shields.io/github/release/esp8266/Arduino.svg)](https://github.com/esp8266/Arduino/releases/latest/) Boards manager link: `https://arduino.esp8266.com/stable/package_esp8266com_index.json` -Documentation: [https://arduino-esp8266.readthedocs.io/en/2.6.3/](https://arduino-esp8266.readthedocs.io/en/2.6.3/) +Documentation: [https://arduino-esp8266.readthedocs.io/en/2.7.0/](https://arduino-esp8266.readthedocs.io/en/2.7.0/) ### Using git version [![Linux build status](https://travis-ci.org/esp8266/Arduino.svg)](https://travis-ci.org/esp8266/Arduino) diff --git a/package.json b/package.json index d02c4c04c..20bad37f5 100644 --- a/package.json +++ b/package.json @@ -2,5 +2,5 @@ "name": "framework-arduinoespressif8266", "description": "Arduino Wiring-based Framework (ESP8266 Core)", "url": "https://github.com/esp8266/Arduino", - "version": "2.7.0" + "version": "3.0.0-dev" } diff --git a/platform.txt b/platform.txt index b507fbaec..751d9c9b5 100644 --- a/platform.txt +++ b/platform.txt @@ -5,8 +5,8 @@ # For more info: # https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5-3rd-party-Hardware-specification -name=ESP8266 Boards (2.7.0) -version=2.7.0 +name=ESP8266 Boards (3.0.0-dev) +version=3.0.0-dev # These will be removed by the packager script when doing a JSON release runtime.tools.xtensa-lx106-elf-gcc.path={runtime.platform.path}/tools/xtensa-lx106-elf From bf718c39afe88f9fc2721a25028ca96665b006d7 Mon Sep 17 00:00:00 2001 From: Takayuki 'January June' Suwa Date: Mon, 4 May 2020 03:52:35 +0900 Subject: [PATCH 21/29] Revert "Changing listen to listen the current iface only instead of 0" (#7266) workaround for #7262 (reverts #7217) Co-authored-by: Takayuki 'January June' Suwa --- libraries/ESP8266mDNS/src/LEAmDNS_Helpers.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP8266mDNS/src/LEAmDNS_Helpers.cpp b/libraries/ESP8266mDNS/src/LEAmDNS_Helpers.cpp index e6d39914f..d23941ce5 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS_Helpers.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS_Helpers.cpp @@ -226,7 +226,7 @@ bool MDNSResponder::_allocUDPContext(void) m_pUDPContext = new UdpContext; m_pUDPContext->ref(); - if (m_pUDPContext->listen(&m_netif->ip_addr, DNS_MQUERY_PORT)) + if (m_pUDPContext->listen(IP4_ADDR_ANY, DNS_MQUERY_PORT)) { m_pUDPContext->setMulticastTTL(MDNS_MULTICAST_TTL); m_pUDPContext->onRx(std::bind(&MDNSResponder::_callProcess, this)); From 9845deb2832bb28bf355d5ff652eb7908977b443 Mon Sep 17 00:00:00 2001 From: xsrf Date: Sun, 3 May 2020 22:00:18 +0200 Subject: [PATCH 22/29] Document USTX in USS/UxS not working as expected (#7265) Documentation that bit USTX in UART status register USS(u) / U0S / U1S is not mirroring TX level as expected but always reads 0, see issue #7256 Co-authored-by: Develo --- cores/esp8266/esp8266_peri.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/esp8266/esp8266_peri.h b/cores/esp8266/esp8266_peri.h index 4d9b8406f..1445b22db 100644 --- a/cores/esp8266/esp8266_peri.h +++ b/cores/esp8266/esp8266_peri.h @@ -250,7 +250,7 @@ extern volatile uint32_t* const esp8266_gpioToFn[16]; #define UIFF 0 //RX FIFO Full //UART STATUS Registers Bits -#define USTX 31 //TX PIN Level +#define USTX 31 //TX PIN Level (Doesn't seem to work, always reads as 0 for both uarts. HW bug? Possible workaround: Enable loopback UxC0 |= 1< Date: Mon, 4 May 2020 11:22:50 -0700 Subject: [PATCH 23/29] Deprecate SPIFFS, move examples to LittleFS (#7263) * Deprecate SPIFFS, move examples to LittleFS SPIFFS has been a great filesystem, but it has significant problems in many cases (and it's also pretty slow). Development seems to have slowed/stopped on the upstream version, and we're not able to provide support or fix the known issues with it as-is. Deprecate SPIFFS variable. Update all examples to use LittleFS instead of SPIFFS. Also, minor cleanup on very old examples which has obsolete delays waiting for the Serial port to come up, or which were stuck at 9600 baud because of their ancient AVR heritage. Fixes #7095 * Remove leftover debug code * Clean up comments in some examples * Update documentation on SPIFFS deprecation * Fix host tests to avoid deprecation warnings * Fix cut-n-paste error * Restore SpeedTest.ino, adjust to allow custom FSes Co-authored-by: Develo --- boards.txt | 4 +- cores/esp8266/FS.h | 2 +- cores/esp8266/spiffs_api.cpp | 6 ++- cores/esp8266/spiffs_api.h | 2 +- doc/boards.rst | 2 +- .../bearssl-client-secure-class.rst | 4 +- doc/esp8266wifi/client-secure-class.rst | 11 ++--- doc/faq/readme.rst | 4 +- doc/filesystem.rst | 10 +++++ doc/installing.rst | 2 +- doc/libraries.rst | 2 +- doc/ota_updates/readme.rst | 2 +- .../CaptivePortalAdvanced.ino | 2 +- .../examples/eeprom_read/eeprom_read.ino | 3 -- .../PostHttpClient/PostHttpClient.ino | 32 +++++++------- .../examples/FSBrowser/FSBrowser.ino | 4 +- .../HttpHashCredAuth/HttpHashCredAuth.ino | 25 ++++++----- .../BearSSL_CertStore/BearSSL_CertStore.ino | 12 ++--- .../BearSSL_CertStore/certs-from-mozilla.py | 2 +- libraries/ESP8266WiFi/keywords.txt | 2 - libraries/ESP8266WiFi/src/CertStoreBearSSL.h | 2 +- .../examples/httpUpdate/httpUpdate.ino | 30 ++++++------- .../httpUpdateLittleFS.ino} | 30 ++++++------- .../httpUpdateSecure/httpUpdateSecure.ino | 44 +++++++++---------- .../src/ESP8266httpUpdate.cpp | 2 +- .../ESP8266httpUpdate/src/ESP8266httpUpdate.h | 5 ++- .../OTA-mDNS-SPIFFS.ino | 9 ++-- .../data/cl_conf.txt | 0 .../LittleFS/examples/SpeedTest/SpeedTest.ino | 16 ++++--- .../SD/examples/Datalogger/Datalogger.ino | 6 +-- libraries/SD/examples/DumpFile/DumpFile.ino | 6 +-- libraries/SD/examples/Files/Files.ino | 6 +-- libraries/SD/examples/ReadWrite/ReadWrite.ino | 6 +-- .../examples/tftbmp/tftbmp.ino | 2 +- .../examples/ConfigFile/ConfigFile.ino | 7 +-- tests/host/common/MockUART.cpp | 3 ++ tests/host/common/spiffs_mock.cpp | 6 +++ tests/host/core/test_Print.cpp | 13 +++--- tests/host/fs/test_fs.cpp | 4 ++ tools/boards.txt.py | 10 ++--- 40 files changed, 176 insertions(+), 164 deletions(-) rename libraries/ESP8266httpUpdate/examples/{httpUpdateSPIFFS/httpUpdateSPIFFS.ino => httpUpdateLittleFS/httpUpdateLittleFS.ino} (66%) rename libraries/ESP8266mDNS/examples/{OTA-mDNS-SPIFFS => OTA-mDNS-LittleFS}/OTA-mDNS-SPIFFS.ino (96%) rename libraries/ESP8266mDNS/examples/{OTA-mDNS-SPIFFS => OTA-mDNS-LittleFS}/data/cl_conf.txt (100%) diff --git a/boards.txt b/boards.txt index 9499d2354..3d2c355c2 100644 --- a/boards.txt +++ b/boards.txt @@ -4960,7 +4960,7 @@ espinotee.menu.baud.3000000.upload.speed=3000000 wifinfo.name=WifInfo wifinfo.build.board=WIFINFO wifinfo.build.variant=wifinfo -wifinfo.menu.ESPModule.ESP07192=ESP07 (1M/192K SPIFFS) +wifinfo.menu.ESPModule.ESP07192=ESP07 (1M/192K FS) wifinfo.menu.ESPModule.ESP07192.build.board=ESP8266_ESP07 wifinfo.menu.ESPModule.ESP07192.build.flash_ld=eagle.flash.1m192.ld wifinfo.menu.ESPModule.ESP07192.build.flash_size=1M @@ -4968,7 +4968,7 @@ wifinfo.menu.ESPModule.ESP07192.build.spiffs_blocksize=4096 wifinfo.menu.ESPModule.ESP07192.build.spiffs_end=0xFB000 wifinfo.menu.ESPModule.ESP07192.build.spiffs_start=0xCB000 wifinfo.menu.ESPModule.ESP07192.upload.maximum_size=827376 -wifinfo.menu.ESPModule.ESP12=ESP12 (4M/1M SPIFFS) +wifinfo.menu.ESPModule.ESP12=ESP12 (4M/1M FS) wifinfo.menu.ESPModule.ESP12.build.board=ESP8266_ESP12 wifinfo.menu.ESPModule.ESP12.build.flash_ld=eagle.flash.4m1m.ld wifinfo.menu.ESPModule.ESP12.build.flash_size=4M diff --git a/cores/esp8266/FS.h b/cores/esp8266/FS.h index aa4752781..93ef68ed5 100644 --- a/cores/esp8266/FS.h +++ b/cores/esp8266/FS.h @@ -266,7 +266,7 @@ using fs::SPIFFSConfig; #endif //FS_NO_GLOBALS #if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SPIFFS) -extern fs::FS SPIFFS; +extern fs::FS SPIFFS __attribute__((deprecated("SPIFFS has been deprecated. Please consider moving to LittleFS or other filesystems."))); #endif #endif //FS_H diff --git a/cores/esp8266/spiffs_api.cpp b/cores/esp8266/spiffs_api.cpp index 1f0278bfc..df3e44245 100644 --- a/cores/esp8266/spiffs_api.cpp +++ b/cores/esp8266/spiffs_api.cpp @@ -37,8 +37,8 @@ int32_t spiffs_hal_read(uint32_t addr, uint32_t size, uint8_t *dst) { return flash_hal_read(addr, size, dst); } - - +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" namespace spiffs_impl { @@ -149,6 +149,8 @@ extern "C" void spiffs_request_end(void) SPIFFS.end(); } +#pragma GCC diagnostic pop + #endif // ARDUINO #endif // !CORE_MOCK diff --git a/cores/esp8266/spiffs_api.h b/cores/esp8266/spiffs_api.h index a40d59b0a..1ea04849e 100644 --- a/cores/esp8266/spiffs_api.h +++ b/cores/esp8266/spiffs_api.h @@ -171,7 +171,7 @@ public: return false; } _cfg = *static_cast(&cfg); - return true; + return true; } bool begin() override diff --git a/doc/boards.rst b/doc/boards.rst index a27c1d5cf..7ab98e251 100644 --- a/doc/boards.rst +++ b/doc/boards.rst @@ -344,7 +344,7 @@ Parameters in Arduino IDE: ~~~~~~~~~~~~~~~~~~~~~~~~~~ - Card: "WEMOS D1 Mini Lite" -- Flash Size: "1M (512K SPIFFS)" +- Flash Size: "1M (512K FS)" - CPU Frequency: "80 Mhz" Power: diff --git a/doc/esp8266wifi/bearssl-client-secure-class.rst b/doc/esp8266wifi/bearssl-client-secure-class.rst index dc6886342..b145e68a0 100644 --- a/doc/esp8266wifi/bearssl-client-secure-class.rst +++ b/doc/esp8266wifi/bearssl-client-secure-class.rst @@ -102,9 +102,9 @@ The web browser you're using to read this document keeps a list of 100s of certi In many cases your application will know the specific CA it needs to validate web or MQTT servers against (often just a single, self-signing CA private to your institution). Simply load your private CA in a `BearSSL::X509List` and use that as your trust anchor. -However, there are cases where you will not know beforehand which CA you will need (i.e. a user enters a website through a keypad), and you need to keep the list of CAs just like your web browser. In those cases, you need to generate a certificate bundle on the PC while compiling your application, upload the `certs.ar` bundle to SPIFFS or SD when uploading your application binary, and pass it to a `BearSSL::CertStore()` in order to validate TLS peers. +However, there are cases where you will not know beforehand which CA you will need (i.e. a user enters a website through a keypad), and you need to keep the list of CAs just like your web browser. In those cases, you need to generate a certificate bundle on the PC while compiling your application, upload the `certs.ar` bundle to LittleFS or SD when uploading your application binary, and pass it to a `BearSSL::CertStore()` in order to validate TLS peers. -See the `BearSSL_CertStore` example for full details as the `BearSSL::CertStore` requires the creation of a cookie-cutter object for filesystem access (because the SD and SPIFFS filesystems are presently incompatible with each other). At a high level in your `setup()` you will call `BearSSL::initCertStore()` on a global object, and then pass this global certificate store to `client.setCertStore(&gCA)` before every connection attempt to enable it as a validation option. +See the `BearSSL_CertStore` example for full details. Supported Crypto ~~~~~~~~~~~~~~~~ diff --git a/doc/esp8266wifi/client-secure-class.rst b/doc/esp8266wifi/client-secure-class.rst index 22501da4a..597a198cc 100644 --- a/doc/esp8266wifi/client-secure-class.rst +++ b/doc/esp8266wifi/client-secure-class.rst @@ -42,23 +42,24 @@ Load client certificate from file system. .. code:: cpp #include + #include #include #include - const char* certyficateFile = "/client.cer"; + const char* certificateFile = "/client.cer"; *setup() or loop()* .. code:: cpp - if (!SPIFFS.begin()) + if (!LittleFS.begin()) { Serial.println("Failed to mount the file system"); return; } - Serial.printf("Opening %s", certyficateFile); - File crtFile = SPIFFS.open(certyficateFile, "r"); + Serial.printf("Opening %s", certificateFile); + File crtFile = LittleFS.open(certificateFile, "r"); if (!crtFile) { Serial.println(" Failed!"); @@ -66,7 +67,7 @@ Load client certificate from file system. WiFiClientSecure client; - Serial.print("Loading %s", certyficateFile); + Serial.print("Loading %s", certificateFile); if (!client.loadCertificate(crtFile)) { Serial.println(" Failed!"); diff --git a/doc/faq/readme.rst b/doc/faq/readme.rst index ee76138df..fda5fb659 100644 --- a/doc/faq/readme.rst +++ b/doc/faq/readme.rst @@ -79,7 +79,7 @@ perform. It is not listed among libraries verified to work with ESP8266. `Read more `__. -In the IDE, for ESP-12E that has 4M flash, I can choose 4M (1M SPIFFS) or 4M (3M SPIFFS). No matter what I select, the IDE tells me the maximum code space is about 1M. Where does my flash go? +In the IDE, for ESP-12E that has 4M flash, I can choose 4M (1M FS) or 4M (3M FS). No matter what I select, the IDE tells me the maximum code space is about 1M. Where does my flash go? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The reason we cannot have more than 1MB of code in flash has to do with @@ -90,7 +90,7 @@ total, but switching such "banks" on the fly is not easy and efficient, so we don't bother doing that. Besides, no one has so far complained about 1MB of code space being insufficient for practical purposes. -The option to choose 3M or 1M SPIFFS is to optimize the upload time. +The option to choose 3M or 1M filesystem is to optimize the upload time. Uploading 3MB takes a long time so sometimes you can just use 1MB. Other 2MB of flash can still be used with ``ESP.flashRead`` and ``ESP.flashWrite`` APIs if necessary. diff --git a/doc/filesystem.rst b/doc/filesystem.rst index abf47a7ea..57b69d383 100644 --- a/doc/filesystem.rst +++ b/doc/filesystem.rst @@ -63,6 +63,16 @@ following include to the sketch: #include "FS.h" +SPIFFS Deprecation Warning +-------------------------- + +SPIFFS is currently deprecated and may be removed in future releases of +the core. Please consider moving your code to LittleFS. SPIFFS is not +actively supported anymore by the upstream developer, while LittleFS is +under active development, supports real directories, and is many times +faster for most operations. + + SPIFFS and LittleFS ------------------- diff --git a/doc/installing.rst b/doc/installing.rst index 515fc891f..cd8e58a7a 100644 --- a/doc/installing.rst +++ b/doc/installing.rst @@ -244,6 +244,6 @@ BeagleBone, CubieBoard). - `What is PlatformIO? `__ - `PlatformIO IDE `__ - `PlatformIO Core `__ (command line tool) -- `Advanced usage `__ - custom settings, uploading to SPIFFS, Over-the-Air (OTA), staging version +- `Advanced usage `__ - custom settings, uploading to LittleFS, Over-the-Air (OTA), staging version - `Integration with Cloud and Standalone IDEs `__ - Cloud9, Codeanywhere, Eclipse Che (Codenvy), Atom, CLion, Eclipse, Emacs, NetBeans, Qt Creator, Sublime Text, VIM, Visual Studio, and VSCode - `Project Examples `__ diff --git a/doc/libraries.rst b/doc/libraries.rst index fc44d46f5..8e8bdcb63 100644 --- a/doc/libraries.rst +++ b/doc/libraries.rst @@ -25,7 +25,7 @@ This is a bit different from standard EEPROM class. You need to call ``EEPROM.be ``EEPROM.write`` does not write to flash immediately, instead you must call ``EEPROM.commit()`` whenever you wish to save changes to flash. ``EEPROM.end()`` will also commit, and will release the RAM copy of EEPROM contents. -EEPROM library uses one sector of flash located just after the SPIFFS. +EEPROM library uses one sector of flash located just after the embedded filesystem. `Three examples `__ included. diff --git a/doc/ota_updates/readme.rst b/doc/ota_updates/readme.rst index a783bc782..35eb93150 100755 --- a/doc/ota_updates/readme.rst +++ b/doc/ota_updates/readme.rst @@ -360,7 +360,7 @@ If this is the case, then most likely ESP module has not been reset after initia The most common causes of OTA failure are as follows: - not enough physical memory on the chip (e.g. ESP01 with 512K flash memory is not enough for OTA). -- too much memory declared for SPIFFS so new sketch will not fit between existing sketch and SPIFFS – see `Update process - memory view <#update-process-memory-view>`__. +- too much memory declared for the filesystem so new sketch will not fit between existing sketch and the filesystem – see `Update process - memory view <#update-process-memory-view>`__. - too little memory declared in Arduino IDE for your selected board (i.e. less than physical size). - not resetting the ESP module after initial upload using serial port. diff --git a/libraries/DNSServer/examples/CaptivePortalAdvanced/CaptivePortalAdvanced.ino b/libraries/DNSServer/examples/CaptivePortalAdvanced/CaptivePortalAdvanced.ino index e97dfbc0a..5f1f5020a 100644 --- a/libraries/DNSServer/examples/CaptivePortalAdvanced/CaptivePortalAdvanced.ino +++ b/libraries/DNSServer/examples/CaptivePortalAdvanced/CaptivePortalAdvanced.ino @@ -56,7 +56,7 @@ unsigned int status = WL_IDLE_STATUS; void setup() { delay(1000); - Serial.begin(9600); + Serial.begin(115200); Serial.println(); Serial.println("Configuring access point..."); /* You can remove the password parameter if you want the AP to be open. */ diff --git a/libraries/EEPROM/examples/eeprom_read/eeprom_read.ino b/libraries/EEPROM/examples/eeprom_read/eeprom_read.ino index 5ffbe8240..3295fecf9 100644 --- a/libraries/EEPROM/examples/eeprom_read/eeprom_read.ino +++ b/libraries/EEPROM/examples/eeprom_read/eeprom_read.ino @@ -15,9 +15,6 @@ byte value; void setup() { // initialize serial and wait for port to open: Serial.begin(115200); - while (!Serial) { - ; // wait for serial port to connect. Needed for Leonardo only - } EEPROM.begin(512); } diff --git a/libraries/ESP8266HTTPClient/examples/PostHttpClient/PostHttpClient.ino b/libraries/ESP8266HTTPClient/examples/PostHttpClient/PostHttpClient.ino index 94d57bdbf..936e235ee 100644 --- a/libraries/ESP8266HTTPClient/examples/PostHttpClient/PostHttpClient.ino +++ b/libraries/ESP8266HTTPClient/examples/PostHttpClient/PostHttpClient.ino @@ -8,8 +8,6 @@ #include #include -#define USE_SERIAL Serial - /* this can be run with an emulated server on host: cd esp8266-core-root-dir cd tests/host @@ -27,21 +25,21 @@ void setup() { - USE_SERIAL.begin(115200); + Serial.begin(115200); - USE_SERIAL.println(); - USE_SERIAL.println(); - USE_SERIAL.println(); + Serial.println(); + Serial.println(); + Serial.println(); WiFi.begin(STASSID, STAPSK); while (WiFi.status() != WL_CONNECTED) { delay(500); - USE_SERIAL.print("."); + Serial.print("."); } - USE_SERIAL.println(""); - USE_SERIAL.print("Connected! IP address: "); - USE_SERIAL.println(WiFi.localIP()); + Serial.println(""); + Serial.print("Connected! IP address: "); + Serial.println(WiFi.localIP()); } @@ -52,29 +50,29 @@ void loop() { WiFiClient client; HTTPClient http; - USE_SERIAL.print("[HTTP] begin...\n"); + Serial.print("[HTTP] begin...\n"); // configure traged server and url http.begin(client, "http://" SERVER_IP "/postplain/"); //HTTP http.addHeader("Content-Type", "application/json"); - USE_SERIAL.print("[HTTP] POST...\n"); + Serial.print("[HTTP] POST...\n"); // start connection and send HTTP header and body int httpCode = http.POST("{\"hello\":\"world\"}"); // httpCode will be negative on error if (httpCode > 0) { // HTTP header has been send and Server response header has been handled - USE_SERIAL.printf("[HTTP] POST... code: %d\n", httpCode); + Serial.printf("[HTTP] POST... code: %d\n", httpCode); // file found at server if (httpCode == HTTP_CODE_OK) { const String& payload = http.getString(); - USE_SERIAL.println("received payload:\n<<"); - USE_SERIAL.println(payload); - USE_SERIAL.println(">>"); + Serial.println("received payload:\n<<"); + Serial.println(payload); + Serial.println(">>"); } } else { - USE_SERIAL.printf("[HTTP] POST... failed, error: %s\n", http.errorToString(httpCode).c_str()); + Serial.printf("[HTTP] POST... failed, error: %s\n", http.errorToString(httpCode).c_str()); } http.end(); diff --git a/libraries/ESP8266WebServer/examples/FSBrowser/FSBrowser.ino b/libraries/ESP8266WebServer/examples/FSBrowser/FSBrowser.ino index beebeb014..f6aff52c7 100644 --- a/libraries/ESP8266WebServer/examples/FSBrowser/FSBrowser.ino +++ b/libraries/ESP8266WebServer/examples/FSBrowser/FSBrowser.ino @@ -25,8 +25,8 @@ // Select the FileSystem by uncommenting one of the lines below -#define USE_SPIFFS -//#define USE_LITTLEFS +//#define USE_SPIFFS +#define USE_LITTLEFS //#define USE_SDFS // Uncomment the following line to embed a version of the web page in the code diff --git a/libraries/ESP8266WebServer/examples/HttpHashCredAuth/HttpHashCredAuth.ino b/libraries/ESP8266WebServer/examples/HttpHashCredAuth/HttpHashCredAuth.ino index e37295b06..c2ceaca53 100644 --- a/libraries/ESP8266WebServer/examples/HttpHashCredAuth/HttpHashCredAuth.ino +++ b/libraries/ESP8266WebServer/examples/HttpHashCredAuth/HttpHashCredAuth.ino @@ -7,10 +7,11 @@ 1. Creating a secure web server using ESP8266ESP8266WebServerSecure 2. Use of HTTP authentication on this secure server 3. A simple web interface to allow an authenticated user to change Credentials - 4. Persisting those credentials through a reboot of the ESP by saving them to SPIFFS without storing them as plain text + 4. Persisting those credentials through a reboot of the ESP by saving them to LittleFS without storing them as plain text */ #include +#include #include #include @@ -23,8 +24,8 @@ const char* ssid = STASSID; const char* wifi_pw = STAPSK; -const String file_credentials = R"(/credentials.txt)"; //SPIFFS file name for the saved credentials -const String change_creds = "changecreds"; //address for a credential change +const String file_credentials = R"(/credentials.txt)"; // LittleFS file name for the saved credentials +const String change_creds = "changecreds"; // Address for a credential change //The ESP8266WebServerSecure requires an encryption certificate and matching key. //These can generated with the bash script available in the ESP8266 Arduino repository. @@ -83,7 +84,7 @@ gz5JWYhbD6c38khSzJb0pNXCo3EuYAVa36kDM96k1BtWuhRS10Q1VXk= ESP8266WebServerSecure server(443); -//These are temporary credentials that will only be used if none are found saved in SPIFFS. +//These are temporary credentials that will only be used if none are found saved in LittleFS. String login = "admin"; const String realm = "global"; String H1 = ""; @@ -92,9 +93,9 @@ String authentication_failed = "User authentication has failed."; void setup() { Serial.begin(115200); - //Initialize SPIFFS to save credentials - if(!SPIFFS.begin()){ - Serial.println("SPIFFS initialization error, programmer flash configured?"); + //Initialize LittleFS to save credentials + if(!LittleFS.begin()){ + Serial.println("LittleFS initialization error, programmer flash configured?"); ESP.restart(); } @@ -187,16 +188,16 @@ void showcredentialpage(){ server.send(200, "text/html", page); } -//Saves credentials to SPIFFS +//Saves credentials to LittleFS void savecredentials(String new_login, String new_password) { //Set global variables to new values login=new_login; H1=ESP8266WebServer::credentialHash(new_login,realm,new_password); - //Save new values to SPIFFS for loading on next reboot + //Save new values to LittleFS for loading on next reboot Serial.println("Saving credentials."); - File f=SPIFFS.open(file_credentials,"w"); //open as a brand new file, discard old contents + File f=LittleFS.open(file_credentials,"w"); //open as a brand new file, discard old contents if(f){ Serial.println("Modifying credentials in file system."); f.println(login); @@ -208,12 +209,12 @@ void savecredentials(String new_login, String new_password) Serial.println("Credentials saved."); } -//loads credentials from SPIFFS +//loads credentials from LittleFS void loadcredentials() { Serial.println("Searching for credentials."); File f; - f=SPIFFS.open(file_credentials,"r"); + f=LittleFS.open(file_credentials,"r"); if(f){ Serial.println("Loading credentials from file system."); String mod=f.readString(); //read the file to a String diff --git a/libraries/ESP8266WiFi/examples/BearSSL_CertStore/BearSSL_CertStore.ino b/libraries/ESP8266WiFi/examples/BearSSL_CertStore/BearSSL_CertStore.ino index 20a8a445a..48bfd0e2c 100644 --- a/libraries/ESP8266WiFi/examples/BearSSL_CertStore/BearSSL_CertStore.ino +++ b/libraries/ESP8266WiFi/examples/BearSSL_CertStore/BearSSL_CertStore.ino @@ -2,11 +2,11 @@ // // Before running, you must download the set of certs using // the script "certs-from-mozilla.py" (no parameters) -// and then uploading the generated .AR file to SPIFFS or SD. +// and then uploading the generated .AR file to LittleFS or SD. // // You do not need to generate the ".IDX" file listed below, // it is generated automatically when the CertStore object -// is created and written to SD or SPIFFS by the ESP8266. +// is created and written to SD or LittleFS by the ESP8266. // // Why would you need a CertStore? // @@ -37,6 +37,7 @@ #include #include #include +#include #ifndef STASSID #define STASSID "your-ssid" @@ -117,8 +118,7 @@ void setup() { Serial.println(); Serial.println(); - SPIFFS.begin(); - // If using a SD card or LittleFS, call the appropriate ::begin instead + LittleFS.begin(); // We start by connecting to a WiFi network Serial.print("Connecting to "); @@ -138,10 +138,10 @@ void setup() { setClock(); // Required for X.509 validation - int numCerts = certStore.initCertStore(SPIFFS, PSTR("/certs.idx"), PSTR("/certs.ar")); + int numCerts = certStore.initCertStore(LittleFS, PSTR("/certs.idx"), PSTR("/certs.ar")); Serial.printf("Number of CA certs read: %d\n", numCerts); if (numCerts == 0) { - Serial.printf("No certs found. Did you run certs-from-mozilla.py and upload the SPIFFS directory before running?\n"); + Serial.printf("No certs found. Did you run certs-from-mozilla.py and upload the LittleFS directory before running?\n"); return; // Can't connect to anything w/o certs! } diff --git a/libraries/ESP8266WiFi/examples/BearSSL_CertStore/certs-from-mozilla.py b/libraries/ESP8266WiFi/examples/BearSSL_CertStore/certs-from-mozilla.py index e423bc25b..556acb70f 100755 --- a/libraries/ESP8266WiFi/examples/BearSSL_CertStore/certs-from-mozilla.py +++ b/libraries/ESP8266WiFi/examples/BearSSL_CertStore/certs-from-mozilla.py @@ -3,7 +3,7 @@ # This script pulls the list of Mozilla trusted certificate authorities # from the web at the "mozurl" below, parses the file to grab the PEM # for each cert, and then generates DER files in a new ./data directory -# Upload these to a SPIFFS filesystem and use the CertManager to parse +# Upload these to an on-chip filesystem and use the CertManager to parse # and use them for your outgoing SSL connections. # # Script by Earle F. Philhower, III. Released to the public domain. diff --git a/libraries/ESP8266WiFi/keywords.txt b/libraries/ESP8266WiFi/keywords.txt index 16eea2422..ec3c0228b 100644 --- a/libraries/ESP8266WiFi/keywords.txt +++ b/libraries/ESP8266WiFi/keywords.txt @@ -23,8 +23,6 @@ BearSSL KEYWORD1 X509List KEYWORD1 PrivateKey KEYWORD1 PublicKey KEYWORD1 -CertStoreSPIFFSBearSSL KEYWORD1 -CertStoreSDBearSSL KEYWORD1 Session KEYWORD1 ESP8266WiFiGratuitous KEYWORD1 diff --git a/libraries/ESP8266WiFi/src/CertStoreBearSSL.h b/libraries/ESP8266WiFi/src/CertStoreBearSSL.h index 76d4966e6..ed3852e2a 100644 --- a/libraries/ESP8266WiFi/src/CertStoreBearSSL.h +++ b/libraries/ESP8266WiFi/src/CertStoreBearSSL.h @@ -26,7 +26,7 @@ #include // Base class for the certificate stores, which allow use -// of a large set of certificates stored on SPIFFS of SD card to +// of a large set of certificates stored on FS or SD card to // be dynamically used when validating a X509 certificate namespace BearSSL { diff --git a/libraries/ESP8266httpUpdate/examples/httpUpdate/httpUpdate.ino b/libraries/ESP8266httpUpdate/examples/httpUpdate/httpUpdate.ino index 69ed71fe6..19c555a23 100644 --- a/libraries/ESP8266httpUpdate/examples/httpUpdate/httpUpdate.ino +++ b/libraries/ESP8266httpUpdate/examples/httpUpdate/httpUpdate.ino @@ -13,8 +13,6 @@ #include #include -#define USE_SERIAL Serial - #ifndef APSSID #define APSSID "APSSID" #define APPSK "APPSK" @@ -24,16 +22,16 @@ ESP8266WiFiMulti WiFiMulti; void setup() { - USE_SERIAL.begin(115200); - // USE_SERIAL.setDebugOutput(true); + Serial.begin(115200); + // Serial.setDebugOutput(true); - USE_SERIAL.println(); - USE_SERIAL.println(); - USE_SERIAL.println(); + Serial.println(); + Serial.println(); + Serial.println(); for (uint8_t t = 4; t > 0; t--) { - USE_SERIAL.printf("[SETUP] WAIT %d...\n", t); - USE_SERIAL.flush(); + Serial.printf("[SETUP] WAIT %d...\n", t); + Serial.flush(); delay(1000); } @@ -44,19 +42,19 @@ void setup() { } void update_started() { - USE_SERIAL.println("CALLBACK: HTTP update process started"); + Serial.println("CALLBACK: HTTP update process started"); } void update_finished() { - USE_SERIAL.println("CALLBACK: HTTP update process finished"); + Serial.println("CALLBACK: HTTP update process finished"); } void update_progress(int cur, int total) { - USE_SERIAL.printf("CALLBACK: HTTP update process at %d of %d bytes...\n", cur, total); + Serial.printf("CALLBACK: HTTP update process at %d of %d bytes...\n", cur, total); } void update_error(int err) { - USE_SERIAL.printf("CALLBACK: HTTP update fatal error code %d\n", err); + Serial.printf("CALLBACK: HTTP update fatal error code %d\n", err); } @@ -86,15 +84,15 @@ void loop() { switch (ret) { case HTTP_UPDATE_FAILED: - USE_SERIAL.printf("HTTP_UPDATE_FAILD Error (%d): %s\n", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str()); + Serial.printf("HTTP_UPDATE_FAILD Error (%d): %s\n", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str()); break; case HTTP_UPDATE_NO_UPDATES: - USE_SERIAL.println("HTTP_UPDATE_NO_UPDATES"); + Serial.println("HTTP_UPDATE_NO_UPDATES"); break; case HTTP_UPDATE_OK: - USE_SERIAL.println("HTTP_UPDATE_OK"); + Serial.println("HTTP_UPDATE_OK"); break; } } diff --git a/libraries/ESP8266httpUpdate/examples/httpUpdateSPIFFS/httpUpdateSPIFFS.ino b/libraries/ESP8266httpUpdate/examples/httpUpdateLittleFS/httpUpdateLittleFS.ino similarity index 66% rename from libraries/ESP8266httpUpdate/examples/httpUpdateSPIFFS/httpUpdateSPIFFS.ino rename to libraries/ESP8266httpUpdate/examples/httpUpdateLittleFS/httpUpdateLittleFS.ino index 8e035fde6..6541d047f 100644 --- a/libraries/ESP8266httpUpdate/examples/httpUpdateSPIFFS/httpUpdateSPIFFS.ino +++ b/libraries/ESP8266httpUpdate/examples/httpUpdateLittleFS/httpUpdateLittleFS.ino @@ -1,5 +1,5 @@ /** - httpUpdateSPIFFS.ino + httpUpdateLittleFS.ino Created on: 05.12.2015 @@ -13,8 +13,6 @@ #include #include -#define USE_SERIAL Serial - ESP8266WiFiMulti WiFiMulti; #ifndef APSSID @@ -24,16 +22,16 @@ ESP8266WiFiMulti WiFiMulti; void setup() { - USE_SERIAL.begin(115200); - // USE_SERIAL.setDebugOutput(true); + Serial.begin(115200); + // Serial.setDebugOutput(true); - USE_SERIAL.println(); - USE_SERIAL.println(); - USE_SERIAL.println(); + Serial.println(); + Serial.println(); + Serial.println(); for (uint8_t t = 4; t > 0; t--) { - USE_SERIAL.printf("[SETUP] WAIT %d...\n", t); - USE_SERIAL.flush(); + Serial.printf("[SETUP] WAIT %d...\n", t); + Serial.flush(); delay(1000); } @@ -46,7 +44,7 @@ void loop() { // wait for WiFi connection if ((WiFiMulti.run() == WL_CONNECTED)) { - USE_SERIAL.println("Update SPIFFS..."); + Serial.println("Update LittleFS..."); WiFiClient client; @@ -58,22 +56,22 @@ void loop() { // value is used to put the LED on. If the LED is on with HIGH, that value should be passed ESPhttpUpdate.setLedPin(LED_BUILTIN, LOW); - t_httpUpdate_return ret = ESPhttpUpdate.updateSpiffs(client, "http://server/spiffs.bin"); + t_httpUpdate_return ret = ESPhttpUpdate.updateFS(client, "http://server/spiffs.bin"); if (ret == HTTP_UPDATE_OK) { - USE_SERIAL.println("Update sketch..."); + Serial.println("Update sketch..."); ret = ESPhttpUpdate.update(client, "http://server/file.bin"); switch (ret) { case HTTP_UPDATE_FAILED: - USE_SERIAL.printf("HTTP_UPDATE_FAILED Error (%d): %s", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str()); + Serial.printf("HTTP_UPDATE_FAILED Error (%d): %s", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str()); break; case HTTP_UPDATE_NO_UPDATES: - USE_SERIAL.println("HTTP_UPDATE_NO_UPDATES"); + Serial.println("HTTP_UPDATE_NO_UPDATES"); break; case HTTP_UPDATE_OK: - USE_SERIAL.println("HTTP_UPDATE_OK"); + Serial.println("HTTP_UPDATE_OK"); break; } } diff --git a/libraries/ESP8266httpUpdate/examples/httpUpdateSecure/httpUpdateSecure.ino b/libraries/ESP8266httpUpdate/examples/httpUpdateSecure/httpUpdateSecure.ino index 9eba1e514..beb613897 100644 --- a/libraries/ESP8266httpUpdate/examples/httpUpdateSecure/httpUpdateSecure.ino +++ b/libraries/ESP8266httpUpdate/examples/httpUpdateSecure/httpUpdateSecure.ino @@ -14,8 +14,7 @@ #include #include - -#define USE_SERIAL Serial +#include #ifndef APSSID #define APSSID "APSSID" @@ -34,46 +33,47 @@ BearSSL::CertStore certStore; void setClock() { configTime(0, 0, "pool.ntp.org", "time.nist.gov"); // UTC - USE_SERIAL.print(F("Waiting for NTP time sync: ")); + Serial.print(F("Waiting for NTP time sync: ")); time_t now = time(nullptr); while (now < 8 * 3600 * 2) { yield(); delay(500); - USE_SERIAL.print(F(".")); + Serial.print(F(".")); now = time(nullptr); } - USE_SERIAL.println(F("")); + Serial.println(F("")); struct tm timeinfo; gmtime_r(&now, &timeinfo); - USE_SERIAL.print(F("Current time: ")); - USE_SERIAL.print(asctime(&timeinfo)); + Serial.print(F("Current time: ")); + Serial.print(asctime(&timeinfo)); } void setup() { - USE_SERIAL.begin(115200); - // USE_SERIAL.setDebugOutput(true); + Serial.begin(115200); + // Serial.setDebugOutput(true); - USE_SERIAL.println(); - USE_SERIAL.println(); - USE_SERIAL.println(); + Serial.println(); + Serial.println(); + Serial.println(); for (uint8_t t = 4; t > 0; t--) { - USE_SERIAL.printf("[SETUP] WAIT %d...\n", t); - USE_SERIAL.flush(); + Serial.printf("[SETUP] WAIT %d...\n", t); + Serial.flush(); delay(1000); } WiFi.mode(WIFI_STA); WiFiMulti.addAP(APSSID, APPSK); - SPIFFS.begin(); + LittleFS.begin(); - int numCerts = certStore.initCertStore(SPIFFS, PSTR("/certs.idx"), PSTR("/certs.ar")); - USE_SERIAL.print(F("Number of CA certs read: ")); USE_SERIAL.println(numCerts); + int numCerts = certStore.initCertStore(LittleFS, PSTR("/certs.idx"), PSTR("/certs.ar")); + Serial.print(F("Number of CA certs read: ")); + Serial.println(numCerts); if (numCerts == 0) { - USE_SERIAL.println(F("No certs found. Did you run certs-from-mozill.py and upload the SPIFFS directory before running?")); + Serial.println(F("No certs found. Did you run certs-from-mozill.py and upload the LittleFS directory before running?")); return; // Can't connect to anything w/o certs! } } @@ -86,7 +86,7 @@ void loop() { BearSSL::WiFiClientSecure client; bool mfln = client.probeMaxFragmentLength("server", 443, 1024); // server must be the same as in ESPhttpUpdate.update() - USE_SERIAL.printf("MFLN supported: %s\n", mfln ? "yes" : "no"); + Serial.printf("MFLN supported: %s\n", mfln ? "yes" : "no"); if (mfln) { client.setBufferSizes(1024, 1024); } @@ -107,15 +107,15 @@ void loop() { switch (ret) { case HTTP_UPDATE_FAILED: - USE_SERIAL.printf("HTTP_UPDATE_FAILED Error (%d): %s\n", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str()); + Serial.printf("HTTP_UPDATE_FAILED Error (%d): %s\n", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str()); break; case HTTP_UPDATE_NO_UPDATES: - USE_SERIAL.println("HTTP_UPDATE_NO_UPDATES"); + Serial.println("HTTP_UPDATE_NO_UPDATES"); break; case HTTP_UPDATE_OK: - USE_SERIAL.println("HTTP_UPDATE_OK"); + Serial.println("HTTP_UPDATE_OK"); break; } } diff --git a/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.cpp b/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.cpp index a7fe1934d..f481d79a6 100755 --- a/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.cpp +++ b/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.cpp @@ -126,7 +126,7 @@ HTTPUpdateResult ESP8266HTTPUpdate::updateSpiffs(const String& url, const String } #endif -HTTPUpdateResult ESP8266HTTPUpdate::updateSpiffs(WiFiClient& client, const String& url, const String& currentVersion) +HTTPUpdateResult ESP8266HTTPUpdate::updateFS(WiFiClient& client, const String& url, const String& currentVersion) { HTTPClient http; http.begin(client, url); diff --git a/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.h b/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.h index ddbd307ad..f1565cd2c 100755 --- a/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.h +++ b/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.h @@ -146,7 +146,10 @@ public: t_httpUpdate_return updateSpiffs(const String& url, const String& currentVersion, const String& httpsFingerprint) __attribute__((deprecated)); t_httpUpdate_return updateSpiffs(const String& url, const String& currentVersion, const uint8_t httpsFingerprint[20]) __attribute__((deprecated)); // BearSSL #endif - t_httpUpdate_return updateSpiffs(WiFiClient& client, const String& url, const String& currentVersion = ""); + t_httpUpdate_return updateFS(WiFiClient& client, const String& url, const String& currentVersion = ""); + t_httpUpdate_return updateSpiffs(WiFiClient& client, const String& url, const String& currentVersion = "") __attribute__((deprecated)) { + return updateFS(client, url, currentVersion); + }; // Notification callbacks void onStart(HTTPUpdateStartCB cbOnStart) { _cbStart = cbOnStart; } diff --git a/libraries/ESP8266mDNS/examples/OTA-mDNS-SPIFFS/OTA-mDNS-SPIFFS.ino b/libraries/ESP8266mDNS/examples/OTA-mDNS-LittleFS/OTA-mDNS-SPIFFS.ino similarity index 96% rename from libraries/ESP8266mDNS/examples/OTA-mDNS-SPIFFS/OTA-mDNS-SPIFFS.ino rename to libraries/ESP8266mDNS/examples/OTA-mDNS-LittleFS/OTA-mDNS-SPIFFS.ino index a03b7cfba..9d0d86147 100644 --- a/libraries/ESP8266mDNS/examples/OTA-mDNS-SPIFFS/OTA-mDNS-SPIFFS.ino +++ b/libraries/ESP8266mDNS/examples/OTA-mDNS-LittleFS/OTA-mDNS-SPIFFS.ino @@ -1,5 +1,5 @@ /** - @file OTA-mDNS-SPIFFS.ino + @file OTA-mDNS-LittleFS.ino @author Pascal Gollor (http://www.pgollor.de/cms/) @date 2015-09-18 @@ -22,6 +22,7 @@ #include #include #include +#include #include @@ -55,7 +56,7 @@ const char* ap_default_psk = STAPSK; ///< Default PSK. */ bool loadConfig(String *ssid, String *pass) { // open file for reading. - File configFile = SPIFFS.open("/cl_conf.txt", "r"); + File configFile = LittleFS.open("/cl_conf.txt", "r"); if (!configFile) { Serial.println("Failed to open cl_conf.txt."); @@ -115,7 +116,7 @@ bool loadConfig(String *ssid, String *pass) { */ bool saveConfig(String *ssid, String *pass) { // Open config file for writing. - File configFile = SPIFFS.open("/cl_conf.txt", "w"); + File configFile = LittleFS.open("/cl_conf.txt", "w"); if (!configFile) { Serial.println("Failed to open cl_conf.txt for writing"); @@ -158,7 +159,7 @@ void setup() { // Initialize file system. - if (!SPIFFS.begin()) { + if (!LittleFS.begin()) { Serial.println("Failed to mount file system"); return; } diff --git a/libraries/ESP8266mDNS/examples/OTA-mDNS-SPIFFS/data/cl_conf.txt b/libraries/ESP8266mDNS/examples/OTA-mDNS-LittleFS/data/cl_conf.txt similarity index 100% rename from libraries/ESP8266mDNS/examples/OTA-mDNS-SPIFFS/data/cl_conf.txt rename to libraries/ESP8266mDNS/examples/OTA-mDNS-LittleFS/data/cl_conf.txt diff --git a/libraries/LittleFS/examples/SpeedTest/SpeedTest.ino b/libraries/LittleFS/examples/SpeedTest/SpeedTest.ino index 5b7dd453a..5987e1080 100644 --- a/libraries/LittleFS/examples/SpeedTest/SpeedTest.ino +++ b/libraries/LittleFS/examples/SpeedTest/SpeedTest.ino @@ -1,8 +1,17 @@ // Simple speed test for filesystem objects // Released to the public domain by Earle F. Philhower, III +#include #include +// Choose the filesystem to test +// WARNING: The filesystem will be formatted at the start of the test! + +#define TESTFS LittleFS +//#define TESTFS SPIFFS +//#define TESTFS SDFS + +// How large of a file to test #define TESTSIZEKB 512 void DoTest(FS *fs) { @@ -112,12 +121,9 @@ void DoTest(FS *fs) { void setup() { Serial.begin(115200); - Serial.printf("Beginning LittleFS test\n"); + Serial.printf("Beginning test\n"); Serial.flush(); - DoTest(&LittleFS); - Serial.printf("Beginning SPIFFS test\n"); - Serial.flush(); - DoTest(&SPIFFS); + DoTest(&TESTFS); } void loop() { diff --git a/libraries/SD/examples/Datalogger/Datalogger.ino b/libraries/SD/examples/Datalogger/Datalogger.ino index fcb7a56e1..a023b6382 100644 --- a/libraries/SD/examples/Datalogger/Datalogger.ino +++ b/libraries/SD/examples/Datalogger/Datalogger.ino @@ -27,11 +27,7 @@ const int chipSelect = 4; void setup() { // Open serial communications and wait for port to open: - Serial.begin(9600); - while (!Serial) { - ; // wait for serial port to connect. Needed for Leonardo only - } - + Serial.begin(115200); Serial.print("Initializing SD card..."); diff --git a/libraries/SD/examples/DumpFile/DumpFile.ino b/libraries/SD/examples/DumpFile/DumpFile.ino index 42bd8bcbb..357465289 100644 --- a/libraries/SD/examples/DumpFile/DumpFile.ino +++ b/libraries/SD/examples/DumpFile/DumpFile.ino @@ -27,11 +27,7 @@ const int chipSelect = 4; void setup() { // Open serial communications and wait for port to open: - Serial.begin(9600); - while (!Serial) { - ; // wait for serial port to connect. Needed for Leonardo only - } - + Serial.begin(115200); Serial.print("Initializing SD card..."); diff --git a/libraries/SD/examples/Files/Files.ino b/libraries/SD/examples/Files/Files.ino index cb2c1f1d2..1c1b83ec2 100644 --- a/libraries/SD/examples/Files/Files.ino +++ b/libraries/SD/examples/Files/Files.ino @@ -24,11 +24,7 @@ File myFile; void setup() { // Open serial communications and wait for port to open: - Serial.begin(9600); - while (!Serial) { - ; // wait for serial port to connect. Needed for Leonardo only - } - + Serial.begin(115200); Serial.print("Initializing SD card..."); diff --git a/libraries/SD/examples/ReadWrite/ReadWrite.ino b/libraries/SD/examples/ReadWrite/ReadWrite.ino index cd63aa554..7aa9a40c4 100644 --- a/libraries/SD/examples/ReadWrite/ReadWrite.ino +++ b/libraries/SD/examples/ReadWrite/ReadWrite.ino @@ -25,11 +25,7 @@ File myFile; void setup() { // Open serial communications and wait for port to open: - Serial.begin(9600); - while (!Serial) { - ; // wait for serial port to connect. Needed for Leonardo only - } - + Serial.begin(115200); Serial.print("Initializing SD card..."); diff --git a/libraries/TFT_Touch_Shield_V2/examples/tftbmp/tftbmp.ino b/libraries/TFT_Touch_Shield_V2/examples/tftbmp/tftbmp.ino index 2bfc9f147..4176de59d 100644 --- a/libraries/TFT_Touch_Shield_V2/examples/tftbmp/tftbmp.ino +++ b/libraries/TFT_Touch_Shield_V2/examples/tftbmp/tftbmp.ino @@ -42,7 +42,7 @@ File bmpFile; void setup() { - Serial.begin(9600); + Serial.begin(115200); pinMode(PIN_SD_CS, OUTPUT); digitalWrite(PIN_SD_CS, HIGH); diff --git a/libraries/esp8266/examples/ConfigFile/ConfigFile.ino b/libraries/esp8266/examples/ConfigFile/ConfigFile.ino index 77bbfabaa..cb1d25be0 100644 --- a/libraries/esp8266/examples/ConfigFile/ConfigFile.ino +++ b/libraries/esp8266/examples/ConfigFile/ConfigFile.ino @@ -9,9 +9,10 @@ #include #include "FS.h" +#include bool loadConfig() { - File configFile = SPIFFS.open("/config.json", "r"); + File configFile = LittleFS.open("/config.json", "r"); if (!configFile) { Serial.println("Failed to open config file"); return false; @@ -56,7 +57,7 @@ bool saveConfig() { doc["serverName"] = "api.example.com"; doc["accessToken"] = "128du9as8du12eoue8da98h123ueh9h98"; - File configFile = SPIFFS.open("/config.json", "w"); + File configFile = LittleFS.open("/config.json", "w"); if (!configFile) { Serial.println("Failed to open config file for writing"); return false; @@ -72,7 +73,7 @@ void setup() { delay(1000); Serial.println("Mounting FS..."); - if (!SPIFFS.begin()) { + if (!LittleFS.begin()) { Serial.println("Failed to mount file system"); return; } diff --git a/tests/host/common/MockUART.cpp b/tests/host/common/MockUART.cpp index 023c775bf..825a4dc0d 100644 --- a/tests/host/common/MockUART.cpp +++ b/tests/host/common/MockUART.cpp @@ -88,7 +88,10 @@ uart_do_write_char(const int uart_nr, char c) } else { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-result" write(uart_nr + 1, &c, 1); +#pragma GCC diagnostic pop w = true; } } diff --git a/tests/host/common/spiffs_mock.cpp b/tests/host/common/spiffs_mock.cpp index 3e85fa06a..8e7362619 100644 --- a/tests/host/common/spiffs_mock.cpp +++ b/tests/host/common/spiffs_mock.cpp @@ -31,6 +31,9 @@ #define SPIFFS_FILE_NAME "spiffs.bin" +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + FS SPIFFS(nullptr); SpiffsMock::SpiffsMock(ssize_t fs_size, size_t fs_block, size_t fs_page, const String& storage) @@ -126,3 +129,6 @@ void SpiffsMock::save () if (::close(fs) == -1) fprintf(stderr, "SPIFFS: closing %s: %s\n", m_storage.c_str(), strerror(errno)); } + +#pragma GCC diagnostic pop + diff --git a/tests/host/core/test_Print.cpp b/tests/host/core/test_Print.cpp index c3cb2f5cd..e58539c59 100644 --- a/tests/host/core/test_Print.cpp +++ b/tests/host/core/test_Print.cpp @@ -16,15 +16,16 @@ #include #include #include -#include "../common/spiffs_mock.h" +#include +#include "../common/littlefs_mock.h" #include -// Use a SPIFFS file because we can't instantiate a virtual class like Print +// Use a LittleFS file because we can't instantiate a virtual class like Print TEST_CASE("Print::write overrides all compile properly", "[core][Print]") { - SPIFFS_MOCK_DECLARE(64, 8, 512, ""); - REQUIRE(SPIFFS.begin()); - auto p = SPIFFS.open("test.bin", "w"); + LITTLEFS_MOCK_DECLARE(64, 8, 512, ""); + REQUIRE(LittleFS.begin()); + auto p = LittleFS.open("test.bin", "w"); REQUIRE(p); uint8_t uint8 = 1; uint16_t uint16 = 2; @@ -56,7 +57,7 @@ TEST_CASE("Print::write overrides all compile properly", "[core][Print]") p.write(1); p.close(); - p = SPIFFS.open("test.bin", "r"); + p = LittleFS.open("test.bin", "r"); REQUIRE(p); uint8_t buff[16]; int len = p.read(buff, 16); diff --git a/tests/host/fs/test_fs.cpp b/tests/host/fs/test_fs.cpp index 28dd0bab5..fb73a6a27 100644 --- a/tests/host/fs/test_fs.cpp +++ b/tests/host/fs/test_fs.cpp @@ -26,6 +26,9 @@ namespace spiffs_test { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + #define FSTYPE SPIFFS #define TESTPRE "SPIFFS - " #define TESTPAT "[fs]" @@ -54,6 +57,7 @@ TEST_CASE("SPIFFS checks the config object passed in", "[fs]") REQUIRE_FALSE(SPIFFS.setConfig(d)); REQUIRE_FALSE(LittleFS.setConfig(l)); } +#pragma GCC diagnostic pop }; diff --git a/tools/boards.txt.py b/tools/boards.txt.py index d33cbe1fc..826319149 100755 --- a/tools/boards.txt.py +++ b/tools/boards.txt.py @@ -29,7 +29,7 @@ # resetmethod_menu_extra menus for additional reset methods # crystalfreq/flashfreq_menu: menus for crystal/flash frequency selection # flashmode_menu: menus for flashmode selection (dio/dout/qio/qout) -# 512K/1M/2M/4M/8M/16M: menus for flash & SPIFFS size +# 512K/1M/2M/4M/8M/16M: menus for flash & FS size # lwip/lwip2 menus for available lwip versions from __future__ import print_function @@ -613,7 +613,7 @@ boards = collections.OrderedDict([ '~~~~~~~~~~~~~~~~~~~~~~~~~~', '', '- Card: "WEMOS D1 Mini Lite"', - '- Flash Size: "1M (512K SPIFFS)"', + '- Flash Size: "1M (512K FS)"', '- CPU Frequency: "80 Mhz"', # '- Upload Speed: "230400"', '', @@ -696,7 +696,7 @@ boards = collections.OrderedDict([ 'opts': collections.OrderedDict([ ( '.build.board', 'WIFINFO' ), ( '.build.variant', 'wifinfo' ), - ( '.menu.ESPModule.ESP07192', 'ESP07 (1M/192K SPIFFS)' ), + ( '.menu.ESPModule.ESP07192', 'ESP07 (1M/192K FS)' ), ( '.menu.ESPModule.ESP07192.build.board', 'ESP8266_ESP07' ), ( '.menu.ESPModule.ESP07192.build.flash_size', '1M' ), ( '.menu.ESPModule.ESP07192.build.flash_ld', 'eagle.flash.1m192.ld' ), @@ -704,7 +704,7 @@ boards = collections.OrderedDict([ ( '.menu.ESPModule.ESP07192.build.spiffs_end', '0xFB000' ), ( '.menu.ESPModule.ESP07192.build.spiffs_blocksize', '4096' ), ( '.menu.ESPModule.ESP07192.upload.maximum_size', '827376' ), - ( '.menu.ESPModule.ESP12', 'ESP12 (4M/1M SPIFFS)' ), + ( '.menu.ESPModule.ESP12', 'ESP12 (4M/1M FS)' ), ( '.menu.ESPModule.ESP12.build.board', 'ESP8266_ESP12' ), ( '.menu.ESPModule.ESP12.build.flash_size', '4M' ), ( '.menu.ESPModule.ESP12.build.flash_ld', 'eagle.flash.4m1m.ld' ), @@ -1316,7 +1316,7 @@ def flash_map (flashsize_kb, fs_kb = 0): else: fs_blocksize = 8192 - # Adjust SPIFFS_end to be a multiple of the block size + # Adjust FS_end to be a multiple of the block size fs_end = fs_blocksize * (int)((fs_end - fs_start)/fs_blocksize) + fs_start; max_ota_size = min(max_upload_size, fs_start / 2) # =(max_upload_size+empty_size)/2 From ea879b6ef64fb3be4f1d1337dd248025eb9d9ee2 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Thu, 7 May 2020 15:32:16 +0200 Subject: [PATCH 24/29] XMC in eboot: hotfix: disabling (#7277) --- bootloaders/eboot/eboot.c | 2 +- bootloaders/eboot/eboot.elf | Bin 36640 -> 34780 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/bootloaders/eboot/eboot.c b/bootloaders/eboot/eboot.c index 39b14307a..edbd92aa5 100644 --- a/bootloaders/eboot/eboot.c +++ b/bootloaders/eboot/eboot.c @@ -190,7 +190,7 @@ int copy_raw(const uint32_t src_addr, return 0; } -#define XMC_SUPPORT +//#define XMC_SUPPORT #ifdef XMC_SUPPORT // Define a few SPI0 registers we need access to #define ESP8266_REG(addr) *((volatile uint32_t *)(0x60000000+(addr))) diff --git a/bootloaders/eboot/eboot.elf b/bootloaders/eboot/eboot.elf index bbd4452c0e5b418cb3a065daba765d68d3e541f2..f9a641c2f37fa922c86ed25b7a6ec080d810b940 100755 GIT binary patch literal 34780 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 From a5432625d93f60d7e28cfdc5ed8abb3e0151951d Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Thu, 7 May 2020 13:06:41 -0700 Subject: [PATCH 25/29] Release 2.7.1 (#7278) --- cores/esp8266/TZ.h | 2 +- package.json | 2 +- platform.txt | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cores/esp8266/TZ.h b/cores/esp8266/TZ.h index f3a490156..2df8272b8 100644 --- a/cores/esp8266/TZ.h +++ b/cores/esp8266/TZ.h @@ -1,7 +1,7 @@ // autogenerated from https://raw.githubusercontent.com/nayarsystems/posix_tz_db/master/zones.csv // by script /tools/TZupdate.sh -// Fri May 1 20:39:05 UTC 2020 +// Thu May 7 19:02:21 UTC 2020 // // This database is autogenerated from IANA timezone database // https://www.iana.org/time-zones diff --git a/package.json b/package.json index 20bad37f5..f1881e45c 100644 --- a/package.json +++ b/package.json @@ -2,5 +2,5 @@ "name": "framework-arduinoespressif8266", "description": "Arduino Wiring-based Framework (ESP8266 Core)", "url": "https://github.com/esp8266/Arduino", - "version": "3.0.0-dev" + "version": "2.7.1" } diff --git a/platform.txt b/platform.txt index 751d9c9b5..401ab558d 100644 --- a/platform.txt +++ b/platform.txt @@ -5,8 +5,8 @@ # For more info: # https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5-3rd-party-Hardware-specification -name=ESP8266 Boards (3.0.0-dev) -version=3.0.0-dev +name=ESP8266 Boards (2.7.1) +version=2.7.1 # These will be removed by the packager script when doing a JSON release runtime.tools.xtensa-lx106-elf-gcc.path={runtime.platform.path}/tools/xtensa-lx106-elf From 56bbb99e6f12b5425d45246907b7496d61b4bd9d Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Thu, 7 May 2020 14:37:44 -0700 Subject: [PATCH 26/29] Back to 3.0.0-dev (#7279) --- package.json | 2 +- platform.txt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index f1881e45c..20bad37f5 100644 --- a/package.json +++ b/package.json @@ -2,5 +2,5 @@ "name": "framework-arduinoespressif8266", "description": "Arduino Wiring-based Framework (ESP8266 Core)", "url": "https://github.com/esp8266/Arduino", - "version": "2.7.1" + "version": "3.0.0-dev" } diff --git a/platform.txt b/platform.txt index 401ab558d..751d9c9b5 100644 --- a/platform.txt +++ b/platform.txt @@ -5,8 +5,8 @@ # For more info: # https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5-3rd-party-Hardware-specification -name=ESP8266 Boards (2.7.1) -version=2.7.1 +name=ESP8266 Boards (3.0.0-dev) +version=3.0.0-dev # These will be removed by the packager script when doing a JSON release runtime.tools.xtensa-lx106-elf-gcc.path={runtime.platform.path}/tools/xtensa-lx106-elf From 7f3d837e577a46af219de6d48f4d29de3e45b7b1 Mon Sep 17 00:00:00 2001 From: Develo Date: Sun, 10 May 2020 21:48:38 -0400 Subject: [PATCH 27/29] Update release instructions (#7292) * Update release instructions to reflect reality, add copy-paste checklist to ease issue. --- package/README.md | 96 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 74 insertions(+), 22 deletions(-) diff --git a/package/README.md b/package/README.md index 76157a420..b9bbdcfe6 100644 --- a/package/README.md +++ b/package/README.md @@ -41,9 +41,9 @@ The purpose of the release process is to generate the following outputs from the * Github Release for esp8266/Arduino project. This is used to host the boards manager package mentioned above, and also contains the release notes. -Here is an overview of the release process. See the section below for detailed instructions. +Here is a rough overview of the effective release process. See the section below for detailed instructions. -1. Release process starts when a maintainer pushes a tag into the repository. +1. Release process effectively starts when a maintainer pushes a tag into the repository. 2. Travis CI runs a build for this tag, and one of the jobs (with `BUILD_TYPE=package`) is used to prepare the boards manager package. This job runs `build_boards_manager_package.sh`. @@ -65,9 +65,11 @@ Here is an overview of the release process. See the section below for detailed i ## Creating a release (for maintainers) -0. Open a new issue to track activities, which will be closed after the release is done. +1. [Open a new issue](https://github.com/esp8266/Arduino/issues/new/choose) to track activities, which will be closed after the release is done. Copy the checklist below into it, and check the steps one by one as they get completed. -1. Assemble release notes. +2. Make sure that no issues or PRs are assigned to the milestone to be released. If there are any Issues/PRs assigned to the relevant milestone, they should either be addressed, pushed back to a future milestone, or closed. + +3. Assemble release notes. * Since most changes are integrated into master using squash-rebase policy (i.e. one commit per PR), `git log --oneline` gives a good overview of changes in the release. @@ -99,22 +101,24 @@ Here is an overview of the release process. See the section below for detailed i * Not all commit descriptions which come from `git log` will explain changes well. Reword items as necessary, with the goal that a general user of this project should be able to understand what the change is related to. Preserve references to PRs or issues (`#XXXX`). - * Don't include fixes for regressions which have been introduced since last release. - * Aggregate minor fixes (e.g. typos, small documentation changes) in a few items. Focus on preparing a good overview of the release for the users, rather than mentioning every change. - * When done, put release notes into a private Gist and send the link to other maintainers for review. + * When done, put release notes into a private [Gist](https://gist.github.com) or [firepad](https://demo.firepad.io) and send the link to other maintainers for review. The following points assume work in a direct clone of the repository, and not in a personal fork. -2. Make a PR with the following, wait for Travis CI, and merge. +4. Make a PR with the following, [wait for Travis CI](https://travis-ci.org/github/esp8266/Arduino/builds/), and merge. - * platform.txt and package.json: update `version` to the release E.g. `3.0.0`, + * [platform.txt](https://github.com/esp8266/Arduino/blob/master/platform.txt) and [package.json](https://github.com/esp8266/Arduino/blob/master/package.json): update `version` to the release E.g. `3.0.0`, - * `cores/esp8266/TZ.h`: import the latest database with the following shell command:\ + * [cores/esp8266/TZ.h](https://github.com/esp8266/Arduino/blob/master/cores/esp8266/TZ.h): import the latest database with the following shell command:\ `$ cd tools; sh TZupdate.sh`. -3. Tag the latest commit on the master branch. In this project, tags have form `X.Y.Z`, e.g. `3.0.0`, or `X.Y.Z-betaN` for release candiate versions. Notice that there's no `v`at the beginning of the tag. Tags must be annotated, not lightweight tags. To create a tag, use git command (assuming that the master branch is checked out): +5. Wait until the release notes have been checked by other maintainers + +6. Navigate to [Travis CI options](https://travis-ci.org/esp8266/Arduino/settings), enable ´Build pushed branches´ (before tagging in next step) + +7. Tag the latest commit on the master branch. In this project, tags have form `X.Y.Z`, e.g. `3.0.0`, or `X.Y.Z-betaN` for release candiate versions. Notice that there's no `v`at the beginning of the tag. Tags must be annotated, not lightweight tags. To create a tag, use git command (assuming that the master branch is checked out): ``` git tag -a -m "Release 3.0.0" 3.0.0 @@ -128,30 +132,78 @@ The following points assume work in a direct clone of the repository, and not in git push origin 3.0.0 ``` -4. In case something goes wrong, release can be canceled at any time: + In case something goes wrong, release can be canceled at any time: * Tag must be removed (`git tag -d X.Y.Z; git push --delete origin X.Y.Z`) * Release must be deleted: github > releases > edit x.y.z > remove all files > delete button appears -5. Wait for Travis CI build for the tag to pass, see https://travis-ci.org/esp8266/Arduino/builds, +8. Wait for Travis CI build for the tag to pass, see https://travis-ci.org/esp8266/Arduino/builds, - return to the Travis CI options and disable `Build pushed branches`. -6. Check that the new (draft) release has been created (no editing at this point!), see https://github.com/esp8266/Arduino/releases. Check that the boards manager package .zip file has been successfully uploaded as a release artifact. +9. Check that the new (draft) release has been created (no editing at this point!), see https://github.com/esp8266/Arduino/releases. -7. Check that the package index downloaded from https://arduino.esp8266.com/stable/package_esp8266com_index.json contains an entry for the new version (it may not be the first one). +10. Check that the boards manager package .zip file has been successfully uploaded as a release artifact. -8. Navigate to release list in Github here https://github.com/esp8266/Arduino/releases, press "Edit" button to edit release description, paste release notes, and publish it. +11. Check that the package index downloaded from https://arduino.esp8266.com/stable/package_esp8266com_index.json contains an entry for the new version (it may not be the first one). -9. In the issue tracker, remove "staged-for-release" label for all issues which have it, and close them. Close the milestone associated with the released version. +12. Return to the [Travis CI options](https://travis-ci.org/esp8266/Arduino/settings) and disable `Build pushed branches`. -10. Check that https://arduino-esp8266.readthedocs.io/en/latest/ has a new doc build for the new tag, and that "stable" points to that build. If a new build did not trigger, log into readthedoc's home here https://readthedocs.org/ (account must have been added to project as maintainer) and trigger it manually. +13. Navigate to release list in Github here https://github.com/esp8266/Arduino/releases, press "Edit" button to edit release description, paste release notes, and publish it. -11. Create a commit to the master branch, updating: +14. In the issue tracker, remove "staged-for-release" label for all issues which have it, and close them. Close the milestone associated with the released version (the milestone should be empty per point 2 above) + +15. Check that https://arduino-esp8266.readthedocs.io/en/latest/ has a new doc build for the new tag, and that "stable" points to that build. If a new build did not trigger, log into readthedoc's home here https://readthedocs.org/ (account must have been added to project as maintainer) and trigger it manually. + +16. Create a commit to the master branch, updating: * The version in platform.txt and package.json files. This should correspond to the version of the *next* milestone, plus `-dev` suffix. E.g. `3.1.0-dev`. - * In main README.md: + * In main README.md go to "Latest release" section, change version number in the readthedocs link to the version which was just released, and verify that all links work. - - in "Latest release" section, change version number in the readthedocs link to the version which was just released, and verify that all links work. + +## Checklist helper, copy-paste into Release Process issue +``` +--------------COPY BELOW THIS LINE-------------- +[Reference](https://github.com/esp8266/Arduino/tree/master/package#creating-a-release-for-maintainers) for details. + +- [ ] 1. Open a new issue to track activities. + +- [ ] 2. Make sure that no issues or PRs are assigned to the milestone to be released. + +- [ ] 3. Assemble release notes. + +- [ ] 4. Make a PR with the following, [wait for Travis CI](https://travis-ci.org/github/esp8266/Arduino/builds/), and merge. + + * [platform.txt](https://github.com/esp8266/Arduino/blob/master/platform.txt) + * [package.json](https://github.com/esp8266/Arduino/blob/master/package.json) + * [cores/esp8266/TZ.h](https://github.com/esp8266/Arduino/blob/master/cores/esp8266/TZ.h) + +- [ ] 5. Wait until the release notes have been checked by other maintainers + +- [ ] 6. Navigate to [Travis CI options](https://travis-ci.org/esp8266/Arduino/settings), enable ´Build pushed branches´ (before tagging in next step) + +- [ ] 7. Tag the latest commit on the master branch, then push it to esp8266/Arduino + +- [ ] 8. Wait for Travis CI build for the tag to pass, see https://travis-ci.org/esp8266/Arduino/builds, + +- [ ] 9. Check that the new (draft) release has been created (no editing at this point!), see https://github.com/esp8266/Arduino/releases. + +- [ ] 10. Check that the boards manager package .zip file has been successfully uploaded as a release artifact. + +- [ ] 11. Check that the package index downloaded from https://arduino.esp8266.com/stable/package_esp8266com_index.json contains an entry for the new version (it may not be the first one). + +- [ ] 12. Return to the [Travis CI options](https://travis-ci.org/esp8266/Arduino/settings) and disable `Build pushed branches`. + +- [ ] 13. Navigate to [release list in Github](https://github.com/esp8266/Arduino/releases), press "Edit" button to edit release description, paste release notes, and publish it. + +- [ ] 14. In the issue tracker, remove "staged-for-release" label for all issues which have it, and close them. Close the milestone associated with the released version (the milestone should be empty per point 2 above) + +- [ ] 15. Check that https://arduino-esp8266.readthedocs.io/en/latest/ has a new doc build for the new tag, and that "stable" points to that build. If a new build did not trigger, log into readthedoc's home here https://readthedocs.org/ (account must have been added to project as maintainer) and trigger it manually. + +- [ ] 16. Create a commit to the master branch, updating: + + * The version in platform.txt and package.json files. This should correspond to the version of the *next* milestone, plus `-dev` suffix. E.g. `3.1.0-dev`. + * In main README.md go to "Latest release" section, change version number in the readthedocs link to the version which was just released, and verify that all links work. +--------------COPY ABOVE THIS LINE-------------- +``` From 9cf2186af1be1167e689aa541f30d6552159838c Mon Sep 17 00:00:00 2001 From: Develo Date: Sun, 10 May 2020 23:01:09 -0400 Subject: [PATCH 28/29] Update version links (#7294) --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 8d4bc4d77..968035915 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Arduino core for ESP8266 WiFi chip # Quick links -- [Latest release documentation](https://arduino-esp8266.readthedocs.io/en/2.7.0/) +- [Latest release documentation](https://arduino-esp8266.readthedocs.io/en/2.7.1/) - [Current "git version" documentation](https://arduino-esp8266.readthedocs.io/en/latest/) - [Install git version](https://arduino-esp8266.readthedocs.io/en/latest/installing.html#using-git-version) ([sources](doc/installing.rst#using-git-version)) @@ -28,7 +28,7 @@ ESP8266 Arduino core comes with libraries to communicate over WiFi using TCP and Starting with 1.6.4, Arduino allows installation of third-party platform packages using Boards Manager. We have packages available for Windows, Mac OS, and Linux (32 and 64 bit). -- Install the current upstream Arduino IDE at the 1.8.7 level or later. The current version is on the [Arduino website](https://www.arduino.cc/en/main/software). +- Install the current upstream Arduino IDE at the 1.8.9 level or later. The current version is on the [Arduino website](https://www.arduino.cc/en/main/software). - Start Arduino and open the Preferences window. - Enter ```https://arduino.esp8266.com/stable/package_esp8266com_index.json``` into the *Additional Board Manager URLs* field. You can add multiple URLs, separating them with commas. - Open Boards Manager from Tools > Board menu and install *esp8266* platform (and don't forget to select your ESP8266 board from Tools > Board menu after installation). @@ -36,7 +36,7 @@ Starting with 1.6.4, Arduino allows installation of third-party platform package #### Latest release [![Latest release](https://img.shields.io/github/release/esp8266/Arduino.svg)](https://github.com/esp8266/Arduino/releases/latest/) Boards manager link: `https://arduino.esp8266.com/stable/package_esp8266com_index.json` -Documentation: [https://arduino-esp8266.readthedocs.io/en/2.7.0/](https://arduino-esp8266.readthedocs.io/en/2.7.0/) +Documentation: [https://arduino-esp8266.readthedocs.io/en/2.7.0/](https://arduino-esp8266.readthedocs.io/en/2.7.1/) ### Using git version [![Linux build status](https://travis-ci.org/esp8266/Arduino.svg)](https://travis-ci.org/esp8266/Arduino) @@ -73,7 +73,7 @@ Documentation for latest development version: https://arduino-esp8266.readthedoc ### Issues and support ### -[ESP8266 Community Forum](https://www.esp8266.com/u/arduinoanswers) is a well-established community for questions and answers about Arduino for ESP8266. If you need help, have a "How do I..." type question, have a problem with a 3rd party library not hosted in this repo, or just want to discuss how to approach a problem, please ask there. +[ESP8266 Community Forum](https://www.esp8266.com/u/arduinoanswers) is a well-established community for questions and answers about Arduino for ESP8266. Stackoverflow is also an alternative. If you need help, have a "How do I..." type question, have a problem with a 3rd party library not hosted in this repo, or just want to discuss how to approach a problem, please ask there. If you find the forum useful, please consider supporting it with a donation.
[![Donate](https://img.shields.io/badge/paypal-donate-yellow.svg)](https://www.paypal.com/webscr?cmd=_s-xclick&hosted_button_id=4M56YCWV6PX66) @@ -96,7 +96,7 @@ For minor fixes of code and documentation, please go ahead and submit a pull req Check out the list of issues that are easy to fix — [easy issues pending](https://github.com/esp8266/Arduino/issues?q=is%3Aopen+is%3Aissue+label%3A%22level%3A+easy%22). Working on them is a great way to move the project forward. -Larger changes (rewriting parts of existing code from scratch, adding new functions to the core, adding new libraries) should generally be discussed by opening an issue first. +Larger changes (rewriting parts of existing code from scratch, adding new functions to the core, adding new libraries) should generally be discussed by opening an issue first. PRs with such changes require testing and approval. Feature branches with lots of small commits (especially titled "oops", "fix typo", "forgot to add file", etc.) should be squashed before opening a pull request. At the same time, please refrain from putting multiple unrelated changes into a single pull request. From ed8add50fe90e5bc434714f8d8f9dbe72d904225 Mon Sep 17 00:00:00 2001 From: Develo Date: Sun, 10 May 2020 23:47:01 -0400 Subject: [PATCH 29/29] Update README.md (#7295) Fix forgotten text edit for the latest version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 968035915..6c4c2f9c2 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Starting with 1.6.4, Arduino allows installation of third-party platform package #### Latest release [![Latest release](https://img.shields.io/github/release/esp8266/Arduino.svg)](https://github.com/esp8266/Arduino/releases/latest/) Boards manager link: `https://arduino.esp8266.com/stable/package_esp8266com_index.json` -Documentation: [https://arduino-esp8266.readthedocs.io/en/2.7.0/](https://arduino-esp8266.readthedocs.io/en/2.7.1/) +Documentation: [https://arduino-esp8266.readthedocs.io/en/2.7.1/](https://arduino-esp8266.readthedocs.io/en/2.7.1/) ### Using git version [![Linux build status](https://travis-ci.org/esp8266/Arduino.svg)](https://travis-ci.org/esp8266/Arduino)