diff --git a/libraries/ArduinoOTA/examples/BasicOTA/BasicOTA.ino b/libraries/ArduinoOTA/examples/BasicOTA/BasicOTA.ino new file mode 100644 index 00000000000..d70da1fbe0a --- /dev/null +++ b/libraries/ArduinoOTA/examples/BasicOTA/BasicOTA.ino @@ -0,0 +1,65 @@ +#include +#include +#include +#include + +const char* ssid = ".........."; +const char* password = ".........."; + +void setup() { + Serial.begin(115200); + Serial.println("Booting"); + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + while (WiFi.waitForConnectResult() != WL_CONNECTED) { + Serial.println("Connection Failed! Rebooting..."); + delay(5000); + ESP.restart(); + } + + // Port defaults to 3232 + // ArduinoOTA.setPort(3232); + + // Hostname defaults to esp3232-[MAC] + // ArduinoOTA.setHostname("myesp32"); + + // No authentication by default + // ArduinoOTA.setPassword("admin"); + + // Password can be set with it's md5 value as well + // MD5(admin) = 21232f297a57a5a743894a0e4a801fc3 + // ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3"); + + ArduinoOTA.onStart([]() { + String type; + if (ArduinoOTA.getCommand() == U_FLASH) + type = "sketch"; + else // U_SPIFFS + type = "filesystem"; + + // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() + Serial.println("Start updating " + type); + }); + ArduinoOTA.onEnd([]() { + Serial.println("\nEnd"); + }); + ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { + Serial.printf("Progress: %u%%\r", (progress / (total / 100))); + }); + ArduinoOTA.onError([](ota_error_t error) { + Serial.printf("Error[%u]: ", error); + if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); + else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); + else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); + else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); + else if (error == OTA_END_ERROR) Serial.println("End Failed"); + }); + ArduinoOTA.begin(); + Serial.println("Ready"); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); +} + +void loop() { + ArduinoOTA.handle(); +} diff --git a/libraries/ArduinoOTA/keywords.txt b/libraries/ArduinoOTA/keywords.txt new file mode 100644 index 00000000000..e9f51798e1e --- /dev/null +++ b/libraries/ArduinoOTA/keywords.txt @@ -0,0 +1,26 @@ +####################################### +# Syntax Coloring Map For Ultrasound +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +ArduinoOTA KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +begin KEYWORD2 +setup KEYWORD2 +handle KEYWORD2 +onStart KEYWORD2 +onEnd KEYWORD2 +onError KEYWORD2 +onProgress KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### + diff --git a/libraries/ArduinoOTA/library.properties b/libraries/ArduinoOTA/library.properties new file mode 100644 index 00000000000..22d78c55a7b --- /dev/null +++ b/libraries/ArduinoOTA/library.properties @@ -0,0 +1,9 @@ +name=ArduinoOTA +version=1.0 +author=Ivan Grokhotkov and Hristo Gochkov +maintainer=Hristo Gochkov +sentence=Enables Over The Air upgrades, via wifi and espota.py UDP request/TCP download. +paragraph=With this library you can enable your sketch to be upgraded over network. Includes mdns anounces to get discovered by the arduino IDE. +category=Communication +url= +architectures=esp32 diff --git a/libraries/ArduinoOTA/src/ArduinoOTA.cpp b/libraries/ArduinoOTA/src/ArduinoOTA.cpp new file mode 100644 index 00000000000..ee5ce12b38d --- /dev/null +++ b/libraries/ArduinoOTA/src/ArduinoOTA.cpp @@ -0,0 +1,367 @@ +#ifndef LWIP_OPEN_SRC +#define LWIP_OPEN_SRC +#endif +#include +#include +#include "ArduinoOTA.h" +#include "ESPmDNS.h" +#include "MD5Builder.h" +#include "Update.h" + + +//#define OTA_DEBUG Serial + +ArduinoOTAClass::ArduinoOTAClass() +: _port(0) +, _initialized(false) +, _rebootOnSuccess(true) +, _mdnsEnabled(true) +, _state(OTA_IDLE) +, _size(0) +, _cmd(0) +, _ota_port(0) +, _start_callback(NULL) +, _end_callback(NULL) +, _error_callback(NULL) +, _progress_callback(NULL) +{ +} + +ArduinoOTAClass::~ArduinoOTAClass(){ + _udp_ota.stop(); +} + +void ArduinoOTAClass::onStart(THandlerFunction fn) { + _start_callback = fn; +} + +void ArduinoOTAClass::onEnd(THandlerFunction fn) { + _end_callback = fn; +} + +void ArduinoOTAClass::onProgress(THandlerFunction_Progress fn) { + _progress_callback = fn; +} + +void ArduinoOTAClass::onError(THandlerFunction_Error fn) { + _error_callback = fn; +} + +void ArduinoOTAClass::setPort(uint16_t port) { + if (!_initialized && !_port && port) { + _port = port; + } +} + +void ArduinoOTAClass::setHostname(const char * hostname) { + if (!_initialized && !_hostname.length() && hostname) { + _hostname = hostname; + } +} + +String ArduinoOTAClass::getHostname() { + return _hostname; +} + +void ArduinoOTAClass::setPassword(const char * password) { + if (!_initialized && !_password.length() && password) { + MD5Builder passmd5; + passmd5.begin(); + passmd5.add(password); + passmd5.calculate(); + _password = passmd5.toString(); + } +} + +void ArduinoOTAClass::setPasswordHash(const char * password) { + if (!_initialized && !_password.length() && password) { + _password = password; + } +} + +void ArduinoOTAClass::setRebootOnSuccess(bool reboot){ + _rebootOnSuccess = reboot; +} + +void ArduinoOTAClass::setMdnsEnabled(bool enabled){ + _mdnsEnabled = enabled; +} + +void ArduinoOTAClass::begin() { + if (_initialized){ + log_w("already initialized"); + return; + } + + if (!_port) { + _port = 3232; + } + + if(!_udp_ota.begin(_port)){ + log_e("udp bind failed"); + return; + } + + + if (!_hostname.length()) { + char tmp[20]; + uint8_t mac[6]; + WiFi.macAddress(mac); + sprintf(tmp, "esp32-%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + _hostname = tmp; + } + if(_mdnsEnabled){ + MDNS.begin(_hostname.c_str()); + MDNS.enableArduino(_port, (_password.length() > 0)); + } + _initialized = true; + _state = OTA_IDLE; +#ifdef OTA_DEBUG + OTA_DEBUG.printf("OTA server at: %s.local:%u\n", _hostname.c_str(), _port); +#endif +} + +int ArduinoOTAClass::parseInt(){ + char data[16]; + uint8_t index = 0; + char value; + while(_udp_ota.peek() == ' ') _udp_ota.read(); + while(true){ + value = _udp_ota.peek(); + if(value < '0' || value > '9'){ + data[index++] = '\0'; + return atoi(data); + } + data[index++] = _udp_ota.read(); + } + return 0; +} + +String ArduinoOTAClass::readStringUntil(char end){ + String res = ""; + char value; + while(true){ + value = _udp_ota.read(); + if(value == '\0' || value == end){ + return res; + } + res += value; + } + return res; +} + +void ArduinoOTAClass::_onRx(){ + if (_state == OTA_IDLE) { + int cmd = parseInt(); + if (cmd != U_FLASH && cmd != U_SPIFFS) + return; + _cmd = cmd; + _ota_port = parseInt(); + _size = parseInt(); + _udp_ota.read(); + _md5 = readStringUntil('\n'); + _md5.trim(); + if(_md5.length() != 32){ + return; + } + + if (_password.length()){ + MD5Builder nonce_md5; + nonce_md5.begin(); + nonce_md5.add(String(micros())); + nonce_md5.calculate(); + _nonce = nonce_md5.toString(); + + _udp_ota.beginPacket(_udp_ota.remoteIP(), _udp_ota.remotePort()); + _udp_ota.printf("AUTH %s", _nonce.c_str()); + _udp_ota.endPacket(); + _state = OTA_WAITAUTH; + return; + } else { + _udp_ota.beginPacket(_udp_ota.remoteIP(), _udp_ota.remotePort()); + _udp_ota.print("OK"); + _udp_ota.endPacket(); + _ota_ip = _udp_ota.remoteIP(); + _state = OTA_RUNUPDATE; + } + } else if (_state == OTA_WAITAUTH) { + int cmd = parseInt(); + if (cmd != U_AUTH) { + _state = OTA_IDLE; + return; + } + _udp_ota.read(); + String cnonce = readStringUntil(' '); + String response = readStringUntil('\n'); + if (cnonce.length() != 32 || response.length() != 32) { + _state = OTA_IDLE; + return; + } + + String challenge = _password + ":" + String(_nonce) + ":" + cnonce; + MD5Builder _challengemd5; + _challengemd5.begin(); + _challengemd5.add(challenge); + _challengemd5.calculate(); + String result = _challengemd5.toString(); + + if(result.equals(response)){ + _udp_ota.beginPacket(_udp_ota.remoteIP(), _udp_ota.remotePort()); + _udp_ota.print("OK"); + _udp_ota.endPacket(); + _ota_ip = _udp_ota.remoteIP(); + _state = OTA_RUNUPDATE; + } else { + _udp_ota.beginPacket(_udp_ota.remoteIP(), _udp_ota.remotePort()); + _udp_ota.print("Authentication Failed"); + _udp_ota.endPacket(); + if (_error_callback) _error_callback(OTA_AUTH_ERROR); + _state = OTA_IDLE; + } + } +} + +void ArduinoOTAClass::_runUpdate() { + if (!Update.begin(_size, _cmd)) { +#ifdef OTA_DEBUG + Update.printError(OTA_DEBUG); +#endif + if (_error_callback) { + _error_callback(OTA_BEGIN_ERROR); + } + _state = OTA_IDLE; + return; + } + Update.setMD5(_md5.c_str()); + + if (_start_callback) { + _start_callback(); + } + if (_progress_callback) { + _progress_callback(0, _size); + } + + WiFiClient client; + if (!client.connect(_ota_ip, _ota_port)) { + if (_error_callback) { + _error_callback(OTA_CONNECT_ERROR); + } + _state = OTA_IDLE; + } + + uint32_t written = 0, total = 0, tried = 0; + while (!Update.isFinished() && client.connected()) { + size_t waited = 1000; + size_t available = client.available(); + while (!available && waited){ + delay(1); + waited -=1 ; + available = client.available(); + } + if (!waited){ + if(written && tried++ < 3){ +#ifdef OTA_DEBUG + OTA_DEBUG.printf("Try[%u]: %u\n", tried, written); +#endif + if(!client.printf("%u", written)){ +#ifdef OTA_DEBUG + OTA_DEBUG.printf("failed to respond\n"); +#endif + _state = OTA_IDLE; + break; + } + continue; + } +#ifdef OTA_DEBUG + OTA_DEBUG.printf("Receive Failed\n"); +#endif + if (_error_callback) { + _error_callback(OTA_RECEIVE_ERROR); + } + _state = OTA_IDLE; + Update.abort(); + return; + } + if(!available){ +#ifdef OTA_DEBUG + OTA_DEBUG.printf("No Data: %u\n", waited); +#endif + _state = OTA_IDLE; + break; + } + tried = 0; + static uint8_t buf[1460]; + if(available > 1460){ + available = 1460; + } + size_t r = client.read(buf, available); + if(r != available){ + log_w("didn't read enough! %u != %u", r, available); + } + + written = Update.write(buf, r); + if (written > 0) { + if(written != r){ + log_w("didn't write enough! %u != %u", written, r); + } + if(!client.printf("%u", written)){ +#ifdef OTA_DEBUG + OTA_DEBUG.printf("failed to respond\n"); +#endif + } + total += written; + if(_progress_callback) { + _progress_callback(total, _size); + } + } else { +#ifdef OTA_DEBUG + Update.printError(OTA_DEBUG); +#endif + } + } + + if (Update.end()) { + client.print("OK"); + client.stop(); + delay(10); + if (_end_callback) { + _end_callback(); + } + if(_rebootOnSuccess){ + //let serial/network finish tasks that might be given in _end_callback + delay(100); + ESP.restart(); + } + } else { + if (_error_callback) { + _error_callback(OTA_END_ERROR); + } + Update.printError(client); + client.stop(); + delay(10); +#ifdef OTA_DEBUG + OTA_DEBUG.print("Update ERROR: "); + Update.printError(OTA_DEBUG); +#endif + _state = OTA_IDLE; + } +} + +void ArduinoOTAClass::handle() { + if (_state == OTA_RUNUPDATE) { + _runUpdate(); + _state = OTA_IDLE; + } + if(_udp_ota.parsePacket()){ + _onRx(); + _udp_ota.flush(); + } +} + +int ArduinoOTAClass::getCommand() { + return _cmd; +} + +#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_ARDUINOOTA) +ArduinoOTAClass ArduinoOTA; +#endif diff --git a/libraries/ArduinoOTA/src/ArduinoOTA.h b/libraries/ArduinoOTA/src/ArduinoOTA.h new file mode 100644 index 00000000000..db477237914 --- /dev/null +++ b/libraries/ArduinoOTA/src/ArduinoOTA.h @@ -0,0 +1,103 @@ +#ifndef __ARDUINO_OTA_H +#define __ARDUINO_OTA_H + +#include +#include +#include "Update.h" + +typedef enum { + OTA_IDLE, + OTA_WAITAUTH, + OTA_RUNUPDATE +} ota_state_t; + +typedef enum { + OTA_AUTH_ERROR, + OTA_BEGIN_ERROR, + OTA_CONNECT_ERROR, + OTA_RECEIVE_ERROR, + OTA_END_ERROR +} ota_error_t; + +class ArduinoOTAClass +{ + public: + typedef std::function THandlerFunction; + typedef std::function THandlerFunction_Error; + typedef std::function THandlerFunction_Progress; + + ArduinoOTAClass(); + ~ArduinoOTAClass(); + + //Sets the service port. Default 8266 + void setPort(uint16_t port); + + //Sets the device hostname. Default esp8266-xxxxxx + void setHostname(const char *hostname); + String getHostname(); + + //Sets the password that will be required for OTA. Default NULL + void setPassword(const char *password); + + //Sets the password as above but in the form MD5(password). Default NULL + void setPasswordHash(const char *password); + + //Sets if the device should be rebooted after successful update. Default true + void setRebootOnSuccess(bool reboot); + + //Sets if the device should advertise itself to Arduino IDE. Default true + void setMdnsEnabled(bool enabled); + + //This callback will be called when OTA connection has begun + void onStart(THandlerFunction fn); + + //This callback will be called when OTA has finished + void onEnd(THandlerFunction fn); + + //This callback will be called when OTA encountered Error + void onError(THandlerFunction_Error fn); + + //This callback will be called when OTA is receiving data + void onProgress(THandlerFunction_Progress fn); + + //Starts the ArduinoOTA service + void begin(); + + //Call this in loop() to run the service + void handle(); + + //Gets update command type after OTA has started. Either U_FLASH or U_SPIFFS + int getCommand(); + + private: + int _port; + String _password; + String _hostname; + String _nonce; + WiFiUDP _udp_ota; + bool _initialized; + bool _rebootOnSuccess; + bool _mdnsEnabled; + ota_state_t _state; + int _size; + int _cmd; + int _ota_port; + IPAddress _ota_ip; + String _md5; + + THandlerFunction _start_callback; + THandlerFunction _end_callback; + THandlerFunction_Error _error_callback; + THandlerFunction_Progress _progress_callback; + + void _runUpdate(void); + void _onRx(void); + int parseInt(void); + String readStringUntil(char end); +}; + +#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_ARDUINOOTA) +extern ArduinoOTAClass ArduinoOTA; +#endif + +#endif /* __ARDUINO_OTA_H */ diff --git a/libraries/ESPmDNS/examples/mDNS-SD_Extended/mDNS-SD_Extended.ino b/libraries/ESPmDNS/examples/mDNS-SD_Extended/mDNS-SD_Extended.ino new file mode 100644 index 00000000000..6f2dfddfb3b --- /dev/null +++ b/libraries/ESPmDNS/examples/mDNS-SD_Extended/mDNS-SD_Extended.ino @@ -0,0 +1,80 @@ +/* + ESP8266 mDNS-SD responder and query sample + + This is an example of announcing and finding services. + + Instructions: + - Update WiFi SSID and password as necessary. + - Flash the sketch to two ESP8266 boards + - The last one powered on should now find the other. + */ + +#include +#include + +const char* ssid = "..."; +const char* password = "..."; + +void setup() { + Serial.begin(115200); + WiFi.begin(ssid, password); + while (WiFi.status() != WL_CONNECTED) { + delay(250); + Serial.print("."); + } + Serial.println(""); + Serial.print("Connected to "); + Serial.println(ssid); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + + if (!MDNS.begin("ESP32_Browser")) { + Serial.println("Error setting up MDNS responder!"); + while(1){ + delay(1000); + } + } +} + +void loop() { + browseService("http", "tcp"); + delay(1000); + browseService("arduino", "tcp"); + delay(1000); + browseService("workstation", "tcp"); + delay(1000); + browseService("smb", "tcp"); + delay(1000); + browseService("afpovertcp", "tcp"); + delay(1000); + browseService("ftp", "tcp"); + delay(1000); + browseService("ipp", "tcp"); + delay(1000); + browseService("printer", "tcp"); + delay(10000); +} + +void browseService(const char * service, const char * proto){ + Serial.printf("Browsing for service _%s._%s.local. ... ", service, proto); + int n = MDNS.queryService(service, proto); + if (n == 0) { + Serial.println("no services found"); + } else { + Serial.print(n); + Serial.println(" service(s) found"); + for (int i = 0; i < n; ++i) { + // Print details for each service found + Serial.print(" "); + Serial.print(i + 1); + Serial.print(": "); + Serial.print(MDNS.hostname(i)); + Serial.print(" ("); + Serial.print(MDNS.IP(i)); + Serial.print(":"); + Serial.print(MDNS.port(i)); + Serial.println(")"); + } + } + Serial.println(); +} diff --git a/libraries/ESPmDNS/examples/mDNS_Web_Server/mDNS_Web_Server.ino b/libraries/ESPmDNS/examples/mDNS_Web_Server/mDNS_Web_Server.ino new file mode 100644 index 00000000000..b25e33b9602 --- /dev/null +++ b/libraries/ESPmDNS/examples/mDNS_Web_Server/mDNS_Web_Server.ino @@ -0,0 +1,120 @@ +/* + ESP32 mDNS responder sample + + This is an example of an HTTP server that is accessible + via http://esp32.local URL thanks to mDNS responder. + + Instructions: + - Update WiFi SSID and password as necessary. + - Flash the sketch to the ESP32 board + - Install host software: + - For Linux, install Avahi (http://avahi.org/). + - For Windows, install Bonjour (http://www.apple.com/support/bonjour/). + - For Mac OSX and iOS support is built in through Bonjour already. + - Point your browser to http://esp32.local, you should see a response. + + */ + + +#include +#include +#include + +const char* ssid = "............"; +const char* password = ".............."; + +// TCP server at port 80 will respond to HTTP requests +WiFiServer server(80); + +void setup(void) +{ + Serial.begin(115200); + + // Connect to WiFi network + WiFi.begin(ssid, password); + Serial.println(""); + + // Wait for connection + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println(""); + Serial.print("Connected to "); + Serial.println(ssid); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + + // Set up mDNS responder: + // - first argument is the domain name, in this example + // the fully-qualified domain name is "esp8266.local" + // - second argument is the IP address to advertise + // we send our IP address on the WiFi network + if (!MDNS.begin("esp32")) { + Serial.println("Error setting up MDNS responder!"); + while(1) { + delay(1000); + } + } + Serial.println("mDNS responder started"); + + // Start TCP (HTTP) server + server.begin(); + Serial.println("TCP server started"); + + // Add service to MDNS-SD + MDNS.addService("http", "tcp", 80); +} + +void loop(void) +{ + // Check if a client has connected + WiFiClient client = server.available(); + if (!client) { + return; + } + Serial.println(""); + Serial.println("New client"); + + // Wait for data from client to become available + while(client.connected() && !client.available()){ + delay(1); + } + + // Read the first line of HTTP request + String req = client.readStringUntil('\r'); + + // First line of HTTP request looks like "GET /path HTTP/1.1" + // Retrieve the "/path" part by finding the spaces + int addr_start = req.indexOf(' '); + int addr_end = req.indexOf(' ', addr_start + 1); + if (addr_start == -1 || addr_end == -1) { + Serial.print("Invalid request: "); + Serial.println(req); + return; + } + req = req.substring(addr_start + 1, addr_end); + Serial.print("Request: "); + Serial.println(req); + client.flush(); + + String s; + if (req == "/") + { + IPAddress ip = WiFi.localIP(); + String ipStr = String(ip[0]) + '.' + String(ip[1]) + '.' + String(ip[2]) + '.' + String(ip[3]); + s = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n\r\nHello from ESP32 at "; + s += ipStr; + s += "\r\n\r\n"; + Serial.println("Sending 200"); + } + else + { + s = "HTTP/1.1 404 Not Found\r\n\r\n"; + Serial.println("Sending 404"); + } + client.print(s); + + Serial.println("Done with client"); +} + diff --git a/libraries/ESPmDNS/keywords.txt b/libraries/ESPmDNS/keywords.txt new file mode 100644 index 00000000000..cbc59312026 --- /dev/null +++ b/libraries/ESPmDNS/keywords.txt @@ -0,0 +1,25 @@ +####################################### +# Syntax Coloring Map For Ultrasound +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +ESPmDNS KEYWORD1 +MDNS KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +begin KEYWORD2 +end KEYWORD2 +addService KEYWORD2 +enableArduino KEYWORD2 +disableArduino KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### + diff --git a/libraries/ESPmDNS/library.properties b/libraries/ESPmDNS/library.properties new file mode 100644 index 00000000000..4f9561961b9 --- /dev/null +++ b/libraries/ESPmDNS/library.properties @@ -0,0 +1,9 @@ +name=ESPmDNS +version=1.0 +author=Hristo Gochkov, Ivan Grokhtkov +maintainer=Hristo Gochkov +sentence=ESP32 mDNS Library +paragraph= +category=Communication +url= +architectures=esp32 diff --git a/libraries/ESPmDNS/src/ESPmDNS.cpp b/libraries/ESPmDNS/src/ESPmDNS.cpp new file mode 100644 index 00000000000..e18da71e56f --- /dev/null +++ b/libraries/ESPmDNS/src/ESPmDNS.cpp @@ -0,0 +1,187 @@ +/* + +ESP8266 Multicast DNS (port of CC3000 Multicast DNS library) +Version 1.1 +Copyright (c) 2013 Tony DiCola (tony@tonydicola.com) +ESP8266 port (c) 2015 Ivan Grokhotkov (ivan@esp8266.com) +MDNS-SD Suport 2015 Hristo Gochkov (hristo@espressif.com) +Extended MDNS-SD support 2016 Lars Englund (lars.englund@gmail.com) + + +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. + + */ + +// Important RFC's for reference: +// - DNS request and response: http://www.ietf.org/rfc/rfc1035.txt +// - Multicast DNS: http://www.ietf.org/rfc/rfc6762.txt +// - MDNS-SD: https://tools.ietf.org/html/rfc6763 + +#ifndef LWIP_OPEN_SRC +#define LWIP_OPEN_SRC +#endif + +#include "ESPmDNS.h" +#include +#include "esp_wifi.h" + +MDNSResponder::MDNSResponder() : mdns(NULL), _if(TCPIP_ADAPTER_IF_STA) {} +MDNSResponder::~MDNSResponder() { + end(); +} + +bool MDNSResponder::begin(const char* hostName, tcpip_adapter_if_t tcpip_if, uint32_t ttl){ + _if = tcpip_if; + if(!mdns && mdns_init(_if, &mdns)){ + log_e("Failed starting MDNS"); + return false; + } + _hostname = hostName; + if(mdns_set_hostname(mdns, hostName)) { + log_e("Failed setting MDNS hostname"); + return false; + } + return true; +} + +void MDNSResponder::end() { + if(!mdns){ + return; + } + mdns_free(mdns); + mdns = NULL; +} + +void MDNSResponder::setInstanceName(String name) { + if (name.length() > 63) return; + if(mdns_set_instance(mdns, name.c_str())){ + log_e("Failed setting MDNS instance"); + return; + } +} + +void MDNSResponder::enableArduino(uint16_t port, bool auth){ + const char * arduTxtData[4] = { + "board=" ARDUINO_BOARD, + "tcp_check=no", + "ssh_upload=no", + "auth_upload=no" + }; + if(auth){ + arduTxtData[3] = "auth_upload=yes"; + } + + if(mdns_service_add(mdns, "_arduino", "_tcp", port)) { + log_e("Failed adding Arduino service"); + } else if(mdns_service_txt_set(mdns, "_arduino", "_tcp", 4, arduTxtData)) { + log_e("Failed setting Arduino service TXT"); + } +} + +void MDNSResponder::disableArduino(){ + if(mdns_service_remove(mdns, "_arduino", "_tcp")) { + log_w("Failed removing Arduino service"); + } +} + +void MDNSResponder::enableWorkstation(){ + char winstance[21+_hostname.length()]; + uint8_t mac[6]; + esp_wifi_get_mac((wifi_interface_t)_if, mac); + sprintf(winstance, "%s [%02x:%02x:%02x:%02x:%02x:%02x]", _hostname.c_str(), mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + + if(mdns_service_add(mdns, "_workstation", "_tcp", 9)) { + log_e("Failed adding Workstation service"); + } else if(mdns_service_instance_set(mdns, "_workstation", "_tcp", winstance)) { + log_e("Failed setting Workstation service instance name"); + } +} + +void MDNSResponder::disableWorkstation(){ + if(mdns_service_remove(mdns, "_workstation", "_tcp")) { + log_w("Failed removing Workstation service"); + } +} + +void MDNSResponder::addService(char *name, char *proto, uint16_t port){ + if(mdns_service_add(mdns, name, proto, port)) { + log_e("Failed adding service %s.%s.\n", name, proto); + } +} + +bool MDNSResponder::addServiceTxt(char *name, char *proto, char *key, char *value){ + //ToDo: implement it in IDF. This will set the TXT to one record currently + String txt = String(key) + "=" + String(value); + const char * txt_chr[1] = {txt.c_str()}; + if(mdns_service_txt_set(mdns, name, proto, 1, txt_chr)) { + log_e("Failed setting service TXT"); + return false; + } + return true; +} + +int MDNSResponder::queryService(char *service, char *proto) { + mdns_result_free(mdns); + if(proto){ + char srv[strlen(service)+2]; + char prt[strlen(proto)+2]; + sprintf(srv, "_%s", service); + sprintf(prt, "_%s", proto); + return mdns_query(mdns, srv, prt, 2000); + } + return mdns_query(mdns, service, NULL, 2000); +} + +IPAddress MDNSResponder::queryHost(char *host){ + mdns_result_free(mdns); + if(!mdns_query(mdns, host, NULL, 2000)){ + return IPAddress(); + } + return IP(0); +} + +String MDNSResponder::hostname(int idx) { + const mdns_result_t * result = mdns_result_get(mdns, idx); + if(!result){ + log_e("Result %d not found", idx); + return String(); + } + return String(result->host); +} + +IPAddress MDNSResponder::IP(int idx) { + const mdns_result_t * result = mdns_result_get(mdns, idx); + if(!result){ + log_e("Result %d not found", idx); + return IPAddress(); + } + return IPAddress(result->addr.addr); +} + +uint16_t MDNSResponder::port(int idx) { + const mdns_result_t * result = mdns_result_get(mdns, idx); + if(!result){ + log_e("Result %d not found", idx); + return 0; + } + return result->port; +} + +MDNSResponder MDNS; diff --git a/libraries/ESPmDNS/src/ESPmDNS.h b/libraries/ESPmDNS/src/ESPmDNS.h new file mode 100644 index 00000000000..7c0cd65b0fb --- /dev/null +++ b/libraries/ESPmDNS/src/ESPmDNS.h @@ -0,0 +1,117 @@ +/* +ESP8266 Multicast DNS (port of CC3000 Multicast DNS library) +Version 1.1 +Copyright (c) 2013 Tony DiCola (tony@tonydicola.com) +ESP8266 port (c) 2015 Ivan Grokhotkov (ivan@esp8266.com) +MDNS-SD Suport 2015 Hristo Gochkov (hristo@espressif.com) +Extended MDNS-SD support 2016 Lars Englund (lars.englund@gmail.com) +Rewritten for ESP32 by Hristo Gochkov (hristo@espressif.com) + +This is a simple implementation of multicast DNS query support for an Arduino +running on ESP32 chip. + +Usage: +- Include the ESP32 Multicast DNS library in the sketch. +- Call the begin method in the sketch's setup and provide a domain name (without + the '.local' suffix, i.e. just provide 'foo' to resolve 'foo.local'), and the + Adafruit CC3000 class instance. Optionally provide a time to live (in seconds) + for the DNS record--the default is 1 hour. +- Call the update method in each iteration of the sketch's loop function. + +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 ESP32MDNS_H +#define ESP32MDNS_H + +#include "Arduino.h" +#include "mdns.h" + +//this should be defined at build time +#ifndef ARDUINO_BOARD +#define ARDUINO_BOARD "esp32" +#endif + +class MDNSResponder { +public: + MDNSResponder(); + ~MDNSResponder(); + bool begin(const char* hostName, tcpip_adapter_if_t tcpip_if=TCPIP_ADAPTER_IF_STA, uint32_t ttl=120); + void end(); + + void setInstanceName(String name); + void setInstanceName(const char * name){ + setInstanceName(String(name)); + } + void setInstanceName(char * name){ + setInstanceName(String(name)); + } + + void addService(char *service, char *proto, uint16_t port); + void addService(const char *service, const char *proto, uint16_t port){ + addService((char *)service, (char *)proto, port); + } + void addService(String service, String proto, uint16_t port){ + addService(service.c_str(), proto.c_str(), port); + } + + bool addServiceTxt(char *name, char *proto, char * key, char * value); + void addServiceTxt(const char *name, const char *proto, const char *key,const char * value){ + addServiceTxt((char *)name, (char *)proto, (char *)key, (char *)value); + } + void addServiceTxt(String name, String proto, String key, String value){ + addServiceTxt(name.c_str(), proto.c_str(), key.c_str(), value.c_str()); + } + + void enableArduino(uint16_t port=3232, bool auth=false); + void disableArduino(); + + void enableWorkstation(); + void disableWorkstation(); + + IPAddress queryHost(char *host); + IPAddress queryHost(const char *host){ + return queryHost((char *)host); + } + IPAddress queryHost(String host){ + return queryHost(host.c_str()); + } + + int queryService(char *service, char *proto); + int queryService(const char *service, const char *proto){ + return queryService((char *)service, (char *)proto); + } + int queryService(String service, String proto){ + return queryService(service.c_str(), proto.c_str()); + } + + String hostname(int idx); + IPAddress IP(int idx); + uint16_t port(int idx); + +private: + mdns_server_t * mdns; + tcpip_adapter_if_t _if; + String _hostname; +}; + +extern MDNSResponder MDNS; + +#endif //ESP32MDNS_H diff --git a/libraries/SD/examples/SD_Test/SD_Test.ino b/libraries/SD/examples/SD_Test/SD_Test.ino index d35b03f573c..8e3884236b0 100644 --- a/libraries/SD/examples/SD_Test/SD_Test.ino +++ b/libraries/SD/examples/SD_Test/SD_Test.ino @@ -16,51 +16,6 @@ #include "SD.h" #include "SPI.h" -void setup(){ - Serial.begin(115200); - if(!SD.begin()){ - Serial.println("Card Mount Failed"); - return; - } - uint8_t cardType = SD.cardType(); - - if(cardType == CARD_NONE){ - Serial.println("No SD card attached"); - return; - } - - Serial.print("SD Card Type: "); - if(cardType == CARD_MMC){ - Serial.println("MMC"); - } else if(cardType == CARD_SD){ - Serial.println("SDSC"); - } else if(cardType == CARD_SDHC){ - Serial.println("SDHC"); - } else { - Serial.println("UNKNOWN"); - } - - uint64_t cardSize = SD.cardSize() / (1024 * 1024); - Serial.printf("SD Card Size: %lluMB\n", cardSize); - - listDir(SD, "/", 0); - createDir(SD, "/mydir"); - listDir(SD, "/", 0); - removeDir(SD, "/mydir"); - listDir(SD, "/", 2); - writeFile(SD, "/hello.txt", "Hello "); - appendFile(SD, "/hello.txt", "World!\n"); - readFile(SD, "/hello.txt"); - deleteFile(SD, "/foo.txt"); - renameFile(SD, "/hello.txt", "/foo.txt"); - readFile(SD, "/foo.txt"); - testFileIO(SD, "/test.txt"); -} - -void loop(){ - -} - void listDir(fs::FS &fs, const char * dirname, uint8_t levels){ Serial.printf("Listing directory: %s\n", dirname); @@ -214,3 +169,48 @@ void testFileIO(fs::FS &fs, const char * path){ Serial.printf("%u bytes written for %u ms\n", 2048 * 512, end); file.close(); } + +void setup(){ + Serial.begin(115200); + if(!SD.begin()){ + Serial.println("Card Mount Failed"); + return; + } + uint8_t cardType = SD.cardType(); + + if(cardType == CARD_NONE){ + Serial.println("No SD card attached"); + return; + } + + Serial.print("SD Card Type: "); + if(cardType == CARD_MMC){ + Serial.println("MMC"); + } else if(cardType == CARD_SD){ + Serial.println("SDSC"); + } else if(cardType == CARD_SDHC){ + Serial.println("SDHC"); + } else { + Serial.println("UNKNOWN"); + } + + uint64_t cardSize = SD.cardSize() / (1024 * 1024); + Serial.printf("SD Card Size: %lluMB\n", cardSize); + + listDir(SD, "/", 0); + createDir(SD, "/mydir"); + listDir(SD, "/", 0); + removeDir(SD, "/mydir"); + listDir(SD, "/", 2); + writeFile(SD, "/hello.txt", "Hello "); + appendFile(SD, "/hello.txt", "World!\n"); + readFile(SD, "/hello.txt"); + deleteFile(SD, "/foo.txt"); + renameFile(SD, "/hello.txt", "/foo.txt"); + readFile(SD, "/foo.txt"); + testFileIO(SD, "/test.txt"); +} + +void loop(){ + +} diff --git a/libraries/SD_MMC/examples/SDMMC_Test/SDMMC_Test.ino b/libraries/SD_MMC/examples/SDMMC_Test/SDMMC_Test.ino index add5c01a83e..bfd986810c1 100644 --- a/libraries/SD_MMC/examples/SDMMC_Test/SDMMC_Test.ino +++ b/libraries/SD_MMC/examples/SDMMC_Test/SDMMC_Test.ino @@ -16,51 +16,6 @@ #include "FS.h" #include "SD_MMC.h" -void setup(){ - Serial.begin(115200); - if(!SD_MMC.begin()){ - Serial.println("Card Mount Failed"); - return; - } - uint8_t cardType = SD_MMC.cardType(); - - if(cardType == CARD_NONE){ - Serial.println("No SD_MMC card attached"); - return; - } - - Serial.print("SD_MMC Card Type: "); - if(cardType == CARD_MMC){ - Serial.println("MMC"); - } else if(cardType == CARD_SD){ - Serial.println("SDSC"); - } else if(cardType == CARD_SDHC){ - Serial.println("SDHC"); - } else { - Serial.println("UNKNOWN"); - } - - uint64_t cardSize = SD_MMC.cardSize() / (1024 * 1024); - Serial.printf("SD_MMC Card Size: %lluMB\n", cardSize); - - listDir(SD_MMC, "/", 0); - createDir(SD_MMC, "/mydir"); - listDir(SD_MMC, "/", 0); - removeDir(SD_MMC, "/mydir"); - listDir(SD_MMC, "/", 2); - writeFile(SD_MMC, "/hello.txt", "Hello "); - appendFile(SD_MMC, "/hello.txt", "World!\n"); - readFile(SD_MMC, "/hello.txt"); - deleteFile(SD_MMC, "/foo.txt"); - renameFile(SD_MMC, "/hello.txt", "/foo.txt"); - readFile(SD_MMC, "/foo.txt"); - testFileIO(SD_MMC, "/test.txt"); -} - -void loop(){ - -} - void listDir(fs::FS &fs, const char * dirname, uint8_t levels){ Serial.printf("Listing directory: %s\n", dirname); @@ -214,3 +169,48 @@ void testFileIO(fs::FS &fs, const char * path){ Serial.printf("%u bytes written for %u ms\n", 2048 * 512, end); file.close(); } + +void setup(){ + Serial.begin(115200); + if(!SD_MMC.begin()){ + Serial.println("Card Mount Failed"); + return; + } + uint8_t cardType = SD_MMC.cardType(); + + if(cardType == CARD_NONE){ + Serial.println("No SD_MMC card attached"); + return; + } + + Serial.print("SD_MMC Card Type: "); + if(cardType == CARD_MMC){ + Serial.println("MMC"); + } else if(cardType == CARD_SD){ + Serial.println("SDSC"); + } else if(cardType == CARD_SDHC){ + Serial.println("SDHC"); + } else { + Serial.println("UNKNOWN"); + } + + uint64_t cardSize = SD_MMC.cardSize() / (1024 * 1024); + Serial.printf("SD_MMC Card Size: %lluMB\n", cardSize); + + listDir(SD_MMC, "/", 0); + createDir(SD_MMC, "/mydir"); + listDir(SD_MMC, "/", 0); + removeDir(SD_MMC, "/mydir"); + listDir(SD_MMC, "/", 2); + writeFile(SD_MMC, "/hello.txt", "Hello "); + appendFile(SD_MMC, "/hello.txt", "World!\n"); + readFile(SD_MMC, "/hello.txt"); + deleteFile(SD_MMC, "/foo.txt"); + renameFile(SD_MMC, "/hello.txt", "/foo.txt"); + readFile(SD_MMC, "/foo.txt"); + testFileIO(SD_MMC, "/test.txt"); +} + +void loop(){ + +} diff --git a/libraries/Update/keywords.txt b/libraries/Update/keywords.txt new file mode 100644 index 00000000000..7a56abf25b6 --- /dev/null +++ b/libraries/Update/keywords.txt @@ -0,0 +1,24 @@ +####################################### +# Syntax Coloring Map For Ultrasound +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +Update KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +begin KEYWORD2 +end KEYWORD2 +write KEYWORD2 +writeStream KEYWORD2 +printError KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### + diff --git a/libraries/Update/library.properties b/libraries/Update/library.properties new file mode 100644 index 00000000000..666b1e425b6 --- /dev/null +++ b/libraries/Update/library.properties @@ -0,0 +1,9 @@ +name=Update +version=1.0 +author=Hristo Gochkov +maintainer=Hristo Gochkov +sentence=ESP32 Sketch Update Library +paragraph= +category=Other +url= +architectures=esp32 diff --git a/libraries/Update/src/Update.h b/libraries/Update/src/Update.h new file mode 100644 index 00000000000..6f733d417fd --- /dev/null +++ b/libraries/Update/src/Update.h @@ -0,0 +1,161 @@ +#ifndef ESP8266UPDATER_H +#define ESP8266UPDATER_H + +#include +#include +#include "esp_partition.h" + +#define UPDATE_ERROR_OK (0) +#define UPDATE_ERROR_WRITE (1) +#define UPDATE_ERROR_ERASE (2) +#define UPDATE_ERROR_READ (3) +#define UPDATE_ERROR_SPACE (4) +#define UPDATE_ERROR_SIZE (5) +#define UPDATE_ERROR_STREAM (6) +#define UPDATE_ERROR_MD5 (7) +#define UPDATE_ERROR_MAGIC_BYTE (8) +#define UPDATE_ERROR_ACTIVATE (9) +#define UPDATE_ERROR_NO_PARTITION (10) +#define UPDATE_ERROR_BAD_ARGUMENT (11) +#define UPDATE_ERROR_ABORT (12) + +#define UPDATE_SIZE_UNKNOWN 0xFFFFFFFF + +#define U_FLASH 0 +#define U_SPIFFS 100 +#define U_AUTH 200 + +class UpdateClass { + public: + UpdateClass(); + /* + Call this to check the space needed for the update + Will return false if there is not enough space + */ + bool begin(size_t size=UPDATE_SIZE_UNKNOWN, int command = U_FLASH); + + /* + Writes a buffer to the flash and increments the address + Returns the amount written + */ + size_t write(uint8_t *data, size_t len); + + /* + Writes the remaining bytes from the Stream to the flash + Uses readBytes() and sets UPDATE_ERROR_STREAM on timeout + Returns the bytes written + Should be equal to the remaining bytes when called + Usable for slow streams like Serial + */ + size_t writeStream(Stream &data); + + /* + If all bytes are written + this call will write the config to eboot + and return true + If there is already an update running but is not finished and !evenIfRemainanig + or there is an error + this will clear everything and return false + the last error is available through getError() + evenIfRemaining is helpfull when you update without knowing the final size first + */ + bool end(bool evenIfRemaining = false); + + /* + Aborts the running update + */ + void abort(); + + /* + Prints the last error to an output stream + */ + void printError(Stream &out); + + /* + sets the expected MD5 for the firmware (hexString) + */ + bool setMD5(const char * expected_md5); + + /* + returns the MD5 String of the sucessfully ended firmware + */ + String md5String(void){ return _md5.toString(); } + + /* + populated the result with the md5 bytes of the sucessfully ended firmware + */ + void md5(uint8_t * result){ return _md5.getBytes(result); } + + //Helpers + uint8_t getError(){ return _error; } + void clearError(){ _error = UPDATE_ERROR_OK; } + bool hasError(){ return _error != UPDATE_ERROR_OK; } + bool isRunning(){ return _size > 0; } + bool isFinished(){ return _progress == _size; } + size_t size(){ return _size; } + size_t progress(){ return _progress; } + size_t remaining(){ return _size - _progress; } + + /* + Template to write from objects that expose + available() and read(uint8_t*, size_t) methods + faster than the writeStream method + writes only what is available + */ + template + size_t write(T &data){ + size_t written = 0; + if (hasError() || !isRunning()) + return 0; + + size_t available = data.available(); + while(available) { + if(_bufferLen + available > remaining()){ + available = remaining() - _bufferLen; + } + if(_bufferLen + available > 4096) { + size_t toBuff = 4096 - _bufferLen; + data.read(_buffer + _bufferLen, toBuff); + _bufferLen += toBuff; + if(!_writeBuffer()) + return written; + written += toBuff; + } else { + data.read(_buffer + _bufferLen, available); + _bufferLen += available; + written += available; + if(_bufferLen == remaining()) { + if(!_writeBuffer()) { + return written; + } + } + } + if(remaining() == 0) + return written; + available = data.available(); + } + return written; + } + + private: + void _reset(); + void _abort(uint8_t err); + bool _writeBuffer(); + bool _verifyHeader(uint8_t data); + bool _verifyEnd(); + + uint8_t _error; + uint8_t *_buffer; + size_t _bufferLen; + size_t _size; + uint32_t _progress; + uint32_t _command; + const esp_partition_t* _partition; + + String _target_md5; + MD5Builder _md5; +}; + +extern UpdateClass Update; + +#endif diff --git a/libraries/Update/src/Updater.cpp b/libraries/Update/src/Updater.cpp new file mode 100644 index 00000000000..11676d2745a --- /dev/null +++ b/libraries/Update/src/Updater.cpp @@ -0,0 +1,278 @@ +#include "Update.h" +#include "Arduino.h" +#include "esp_spi_flash.h" +#include "esp_ota_ops.h" +#include "esp_image_format.h" + +static const char * _err2str(uint8_t _error){ + if(_error == UPDATE_ERROR_OK){ + return ("No Error"); + } else if(_error == UPDATE_ERROR_WRITE){ + return ("Flash Write Failed"); + } else if(_error == UPDATE_ERROR_ERASE){ + return ("Flash Erase Failed"); + } else if(_error == UPDATE_ERROR_READ){ + return ("Flash Read Failed"); + } else if(_error == UPDATE_ERROR_SPACE){ + return ("Not Enough Space"); + } else if(_error == UPDATE_ERROR_SIZE){ + return ("Bad Size Given"); + } else if(_error == UPDATE_ERROR_STREAM){ + return ("Stream Read Timeout"); + } else if(_error == UPDATE_ERROR_MD5){ + return ("MD5 Check Failed"); + } else if(_error == UPDATE_ERROR_MAGIC_BYTE){ + return ("Wrong Magic Byte"); + } else if(_error == UPDATE_ERROR_ACTIVATE){ + return ("Could Not Activate The Firmware"); + } else if(_error == UPDATE_ERROR_NO_PARTITION){ + return ("Partition Could Not be Found"); + } else if(_error == UPDATE_ERROR_BAD_ARGUMENT){ + return ("Bad Argument"); + } else if(_error == UPDATE_ERROR_ABORT){ + return ("Aborted"); + } + return ("UNKNOWN"); +} + +UpdateClass::UpdateClass() +: _error(0) +, _buffer(0) +, _bufferLen(0) +, _size(0) +, _progress(0) +, _command(U_FLASH) +, _partition(NULL) +{ +} + +void UpdateClass::_reset() { + if (_buffer) + delete[] _buffer; + _buffer = 0; + _bufferLen = 0; + _progress = 0; + _size = 0; + _command = U_FLASH; +} + +bool UpdateClass::begin(size_t size, int command) { + if(_size > 0){ + log_w("already running"); + return false; + } + + _reset(); + _error = 0; + + if(size == 0) { + _error = UPDATE_ERROR_SIZE; + return false; + } + + if (command == U_FLASH) { + _partition = esp_ota_get_next_update_partition(NULL); + if(!_partition){ + _error = UPDATE_ERROR_NO_PARTITION; + return false; + } + log_d("OTA Partition: %s", _partition->label); + } + else if (command == U_SPIFFS) { + _partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, NULL); + if(!_partition){ + _error = UPDATE_ERROR_NO_PARTITION; + return false; + } + } + else { + _error = UPDATE_ERROR_BAD_ARGUMENT; + log_e("bad command %u", command); + return false; + } + + if(size == UPDATE_SIZE_UNKNOWN){ + size = _partition->size; + } else if(size > _partition->size){ + _error = UPDATE_ERROR_SIZE; + log_e("too large %u > %u", size, _partition->size); + return false; + } + + //initialize + _buffer = (uint8_t*)malloc(SPI_FLASH_SEC_SIZE); + if(!_buffer){ + log_e("malloc failed"); + return false; + } + _size = size; + _command = command; + _md5.begin(); + return true; +} + +void UpdateClass::_abort(uint8_t err){ + _reset(); + _error = err; +} + +void UpdateClass::abort(){ + _abort(UPDATE_ERROR_ABORT); +} + +bool UpdateClass::_writeBuffer(){ + if(!ESP.flashEraseSector((_partition->address + _progress)/SPI_FLASH_SEC_SIZE)){ + _abort(UPDATE_ERROR_ERASE); + return false; + } + if (!ESP.flashWrite(_partition->address + _progress, (uint32_t*)_buffer, _bufferLen)) { + _abort(UPDATE_ERROR_WRITE); + return false; + } + _md5.add(_buffer, _bufferLen); + _progress += _bufferLen; + _bufferLen = 0; + return true; +} + +bool UpdateClass::_verifyHeader(uint8_t data) { + if(_command == U_FLASH) { + if(data != ESP_IMAGE_HEADER_MAGIC) { + _abort(UPDATE_ERROR_MAGIC_BYTE); + return false; + } + return true; + } else if(_command == U_SPIFFS) { + return true; + } + return false; +} + +bool UpdateClass::_verifyEnd() { + if(_command == U_FLASH) { + uint8_t buf[4]; + if(!ESP.flashRead(_partition->address, (uint32_t*)buf, 4)) { + _abort(UPDATE_ERROR_READ); + return false; + } + + if(buf[0] != ESP_IMAGE_HEADER_MAGIC) { + _abort(UPDATE_ERROR_MAGIC_BYTE); + return false; + } + + if(esp_ota_set_boot_partition(_partition)){ + _abort(UPDATE_ERROR_ACTIVATE); + return false; + } + _reset(); + return true; + } else if(_command == U_SPIFFS) { + return true; + } + return false; +} + +bool UpdateClass::setMD5(const char * expected_md5){ + if(strlen(expected_md5) != 32) + { + return false; + } + _target_md5 = expected_md5; + return true; +} + +bool UpdateClass::end(bool evenIfRemaining){ + if(hasError() || _size == 0){ + return false; + } + + if(!isFinished() && !evenIfRemaining){ + log_e("premature end: res:%u, pos:%u/%u\n", getError(), progress(), _size); + _abort(UPDATE_ERROR_ABORT); + return false; + } + + if(evenIfRemaining) { + if(_bufferLen > 0) { + _writeBuffer(); + } + _size = progress(); + } + + _md5.calculate(); + if(_target_md5.length()) { + if(_target_md5 != _md5.toString()){ + _abort(UPDATE_ERROR_MD5); + return false; + } + } + + return _verifyEnd(); +} + +size_t UpdateClass::write(uint8_t *data, size_t len) { + if(hasError() || !isRunning()){ + return 0; + } + + if(len > remaining()){ + _abort(UPDATE_ERROR_SPACE); + return 0; + } + + size_t left = len; + + while((_bufferLen + left) > SPI_FLASH_SEC_SIZE) { + size_t toBuff = SPI_FLASH_SEC_SIZE - _bufferLen; + memcpy(_buffer + _bufferLen, data + (len - left), toBuff); + _bufferLen += toBuff; + if(!_writeBuffer()){ + return len - left; + } + left -= toBuff; + } + memcpy(_buffer + _bufferLen, data + (len - left), left); + _bufferLen += left; + if(_bufferLen == remaining()){ + if(!_writeBuffer()){ + return len - left; + } + } + return len; +} + +size_t UpdateClass::writeStream(Stream &data) { + size_t written = 0; + size_t toRead = 0; + if(hasError() || !isRunning()) + return 0; + + if(!_verifyHeader(data.peek())) { + _reset(); + return 0; + } + + while(remaining()) { + toRead = data.readBytes(_buffer + _bufferLen, (SPI_FLASH_SEC_SIZE - _bufferLen)); + if(toRead == 0) { //Timeout + delay(100); + toRead = data.readBytes(_buffer + _bufferLen, (SPI_FLASH_SEC_SIZE - _bufferLen)); + if(toRead == 0) { //Timeout + _abort(UPDATE_ERROR_STREAM); + return written; + } + } + _bufferLen += toRead; + if((_bufferLen == remaining() || _bufferLen == SPI_FLASH_SEC_SIZE) && !_writeBuffer()) + return written; + written += toRead; + } + return written; +} + +void UpdateClass::printError(Stream &out){ + out.println(_err2str(_error)); +} + +UpdateClass Update; diff --git a/platform.txt b/platform.txt index b654bc7707d..ff129b4349e 100644 --- a/platform.txt +++ b/platform.txt @@ -6,6 +6,9 @@ runtime.tools.xtensa-esp32-elf-gcc.path={runtime.platform.path}/tools/xtensa-esp tools.esptool.cmd="{runtime.platform.path}/tools/esptool" tools.esptool.cmd.windows="{runtime.platform.path}/tools/esptool.exe" +tools.esptool.network_cmd=python "{runtime.platform.path}/tools/espota.py" +tools.esptool.network_cmd.windows="{runtime.platform.path}/tools/espota.exe" + tools.gen_esp32part.cmd=python "{runtime.platform.path}/tools/gen_esp32part.py" tools.gen_esp32part.cmd.windows="{runtime.platform.path}/tools/gen_esp32part.exe" @@ -17,7 +20,7 @@ compiler.warning_flags.all=-Wall -Werror=all -Wextra compiler.path={runtime.tools.xtensa-esp32-elf-gcc.path}/bin/ compiler.sdk.path={runtime.platform.path}/tools/sdk -compiler.cpreprocessor.flags=-DESP_PLATFORM -DMBEDTLS_CONFIG_FILE="mbedtls/esp_config.h" -DHAVE_CONFIG_H "-I{compiler.sdk.path}/include/config" "-I{compiler.sdk.path}/include/bluedroid" "-I{compiler.sdk.path}/include/bt" "-I{compiler.sdk.path}/include/driver" "-I{compiler.sdk.path}/include/esp32" "-I{compiler.sdk.path}/include/ethernet" "-I{compiler.sdk.path}/include/fatfs" "-I{compiler.sdk.path}/include/freertos" "-I{compiler.sdk.path}/include/log" "-I{compiler.sdk.path}/include/mdns" "-I{compiler.sdk.path}/include/mbedtls" "-I{compiler.sdk.path}/include/mbedtls_port" "-I{compiler.sdk.path}/include/vfs" "-I{compiler.sdk.path}/include/ulp" "-I{compiler.sdk.path}/include/newlib" "-I{compiler.sdk.path}/include/nvs_flash" "-I{compiler.sdk.path}/include/spi_flash" "-I{compiler.sdk.path}/include/sdmmc" "-I{compiler.sdk.path}/include/openssl" "-I{compiler.sdk.path}/include/app_update" "-I{compiler.sdk.path}/include/tcpip_adapter" "-I{compiler.sdk.path}/include/xtensa-debug-module" "-I{compiler.sdk.path}/include/newlib" "-I{compiler.sdk.path}/include/coap" "-I{compiler.sdk.path}/include/wpa_supplicant" "-I{compiler.sdk.path}/include/expat" "-I{compiler.sdk.path}/include/json" "-I{compiler.sdk.path}/include/nghttp" "-I{compiler.sdk.path}/include/lwip" +compiler.cpreprocessor.flags=-DESP_PLATFORM -DMBEDTLS_CONFIG_FILE="mbedtls/esp_config.h" -DHAVE_CONFIG_H "-I{compiler.sdk.path}/include/config" "-I{compiler.sdk.path}/include/bluedroid" "-I{compiler.sdk.path}/include/bootloader_support" "-I{compiler.sdk.path}/include/bt" "-I{compiler.sdk.path}/include/driver" "-I{compiler.sdk.path}/include/esp32" "-I{compiler.sdk.path}/include/ethernet" "-I{compiler.sdk.path}/include/fatfs" "-I{compiler.sdk.path}/include/freertos" "-I{compiler.sdk.path}/include/log" "-I{compiler.sdk.path}/include/mdns" "-I{compiler.sdk.path}/include/mbedtls" "-I{compiler.sdk.path}/include/mbedtls_port" "-I{compiler.sdk.path}/include/vfs" "-I{compiler.sdk.path}/include/ulp" "-I{compiler.sdk.path}/include/newlib" "-I{compiler.sdk.path}/include/nvs_flash" "-I{compiler.sdk.path}/include/spi_flash" "-I{compiler.sdk.path}/include/sdmmc" "-I{compiler.sdk.path}/include/openssl" "-I{compiler.sdk.path}/include/app_update" "-I{compiler.sdk.path}/include/tcpip_adapter" "-I{compiler.sdk.path}/include/xtensa-debug-module" "-I{compiler.sdk.path}/include/newlib" "-I{compiler.sdk.path}/include/coap" "-I{compiler.sdk.path}/include/wpa_supplicant" "-I{compiler.sdk.path}/include/expat" "-I{compiler.sdk.path}/include/json" "-I{compiler.sdk.path}/include/nghttp" "-I{compiler.sdk.path}/include/lwip" compiler.c.cmd=xtensa-esp32-elf-gcc compiler.c.flags=-std=gnu99 -Os -g3 -ffunction-sections -fdata-sections -fstrict-volatile-bitfields -mlongcalls -nostdlib -Wpointer-arith {compiler.warning_flags} -Wno-error=unused-function -Wno-error=unused-but-set-variable -Wno-error=unused-variable -Wno-error=deprecated-declarations -Wno-unused-parameter -Wno-sign-compare -Wno-old-style-declaration -MMD -c @@ -88,3 +91,4 @@ tools.esptool.upload.protocol=esp32 tools.esptool.upload.params.verbose= tools.esptool.upload.params.quiet= tools.esptool.upload.pattern={cmd} --chip esp32 --port "{serial.port}" --baud {upload.speed} --before default_reset --after hard_reset write_flash -z --flash_freq {build.flash_freq} --flash_mode {build.flash_mode} --flash_size {build.flash_size} 0x1000 "{runtime.platform.path}/tools/sdk/bin/bootloader.bin" 0x8000 "{build.path}/{build.project_name}.partitions.bin" 0xe000 "{runtime.platform.path}/tools/partitions/boot_app0.bin" 0x10000 "{build.path}/{build.project_name}.bin" +tools.esptool.upload.network_pattern={network_cmd} -i "{serial.port}" -p "{network.port}" "--auth={network.password}" -f "{build.path}/{build.project_name}.bin" diff --git a/tools/espota.exe b/tools/espota.exe new file mode 100644 index 00000000000..f0b6a69f5c4 Binary files /dev/null and b/tools/espota.exe differ diff --git a/tools/espota.py b/tools/espota.py new file mode 100755 index 00000000000..e09c9f124cf --- /dev/null +++ b/tools/espota.py @@ -0,0 +1,321 @@ +#!/usr/bin/env python +# +# Original espota.py by Ivan Grokhotkov: +# https://gist.github.com/igrr/d35ab8446922179dc58c +# +# Modified since 2015-09-18 from Pascal Gollor (https://github.com/pgollor) +# Modified since 2015-11-09 from Hristo Gochkov (https://github.com/me-no-dev) +# Modified since 2016-01-03 from Matthew O'Gorman (https://githumb.com/mogorman) +# +# This script will push an OTA update to the ESP +# use it like: python espota.py -i -I -p -P [-a password] -f +# Or to upload SPIFFS image: +# python espota.py -i -I -p -P [-a password] -s -f +# +# Changes +# 2015-09-18: +# - Add option parser. +# - Add logging. +# - Send command to controller to differ between flashing and transmitting SPIFFS image. +# +# Changes +# 2015-11-09: +# - Added digest authentication +# - Enhanced error tracking and reporting +# +# Changes +# 2016-01-03: +# - Added more options to parser. +# + +from __future__ import print_function +import socket +import sys +import os +import optparse +import logging +import hashlib +import random + +# Commands +FLASH = 0 +SPIFFS = 100 +AUTH = 200 +PROGRESS = False +# update_progress() : Displays or updates a console progress bar +## Accepts a float between 0 and 1. Any int will be converted to a float. +## A value under 0 represents a 'halt'. +## A value at 1 or bigger represents 100% +def update_progress(progress): + if (PROGRESS): + barLength = 60 # Modify this to change the length of the progress bar + status = "" + if isinstance(progress, int): + progress = float(progress) + if not isinstance(progress, float): + progress = 0 + status = "error: progress var must be float\r\n" + if progress < 0: + progress = 0 + status = "Halt...\r\n" + if progress >= 1: + progress = 1 + status = "Done...\r\n" + block = int(round(barLength*progress)) + text = "\rUploading: [{0}] {1}% {2}".format( "="*block + " "*(barLength-block), int(progress*100), status) + sys.stderr.write(text) + sys.stderr.flush() + else: + sys.stderr.write('.') + sys.stderr.flush() + +def serve(remoteAddr, localAddr, remotePort, localPort, password, filename, command = FLASH): + # Create a TCP/IP socket + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server_address = (localAddr, localPort) + logging.info('Starting on %s:%s', str(server_address[0]), str(server_address[1])) + try: + sock.bind(server_address) + sock.listen(1) + except: + logging.error("Listen Failed") + return 1 + + content_size = os.path.getsize(filename) + f = open(filename,'rb') + file_md5 = hashlib.md5(f.read()).hexdigest() + f.close() + logging.info('Upload size: %d', content_size) + message = '%d %d %d %s\n' % (command, localPort, content_size, file_md5) + + # Wait for a connection + inv_trys = 0 + data = '' + msg = 'Sending invitation to %s ' % (remoteAddr) + sys.stderr.write(msg) + sys.stderr.flush() + while (inv_trys < 10): + inv_trys += 1 + sock2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + remote_address = (remoteAddr, int(remotePort)) + sent = sock2.sendto(message.encode(), remote_address) + sock2.settimeout(1) + try: + data = sock2.recv(37).decode() + break; + except: + sys.stderr.write('.') + sys.stderr.flush() + sock2.close() + sys.stderr.write('\n') + sys.stderr.flush() + if (inv_trys == 10): + logging.error('No response from the ESP') + return 1 + if (data != "OK"): + if(data.startswith('AUTH')): + nonce = data.split()[1] + cnonce_text = '%s%u%s%s' % (filename, content_size, file_md5, remoteAddr) + cnonce = hashlib.md5(cnonce_text.encode()).hexdigest() + passmd5 = hashlib.md5(password.encode()).hexdigest() + result_text = '%s:%s:%s' % (passmd5 ,nonce, cnonce) + result = hashlib.md5(result_text.encode()).hexdigest() + sys.stderr.write('Authenticating...') + sys.stderr.flush() + message = '%d %s %s\n' % (AUTH, cnonce, result) + sock2.sendto(message.encode(), remote_address) + sock2.settimeout(10) + try: + data = sock2.recv(32).decode() + except: + sys.stderr.write('FAIL\n') + logging.error('No Answer to our Authentication') + sock2.close() + return 1 + if (data != "OK"): + sys.stderr.write('FAIL\n') + logging.error('%s', data) + sock2.close() + sys.exit(1); + return 1 + sys.stderr.write('OK\n') + else: + logging.error('Bad Answer: %s', data) + sock2.close() + return 1 + sock2.close() + + logging.info('Waiting for device...') + try: + sock.settimeout(10) + connection, client_address = sock.accept() + sock.settimeout(None) + connection.settimeout(None) + except: + logging.error('No response from device') + sock.close() + return 1 + + try: + f = open(filename, "rb") + if (PROGRESS): + update_progress(0) + else: + sys.stderr.write('Uploading') + sys.stderr.flush() + offset = 0 + while True: + chunk = f.read(1460) + if not chunk: break + offset += len(chunk) + update_progress(offset/float(content_size)) + connection.settimeout(10) + try: + connection.sendall(chunk) + res = connection.recv(5) + except: + sys.stderr.write('\n') + logging.error('Error Uploading') + connection.close() + f.close() + sock.close() + return 1 + + sys.stderr.write('\n') + logging.info('Waiting for result...') + try: + connection.settimeout(60) + data = connection.recv(32).decode() + logging.info('Result: %s' ,data) + connection.close() + f.close() + sock.close() + if (data != "OK"): + sys.stderr.write('\n') + logging.error('%s', data) + return 1; + return 0 + except: + logging.error('No Result!') + connection.close() + f.close() + sock.close() + return 1 + + finally: + connection.close() + f.close() + + sock.close() + return 1 +# end serve + + +def parser(unparsed_args): + parser = optparse.OptionParser( + usage = "%prog [options]", + description = "Transmit image over the air to the esp8266 module with OTA support." + ) + + # destination ip and port + group = optparse.OptionGroup(parser, "Destination") + group.add_option("-i", "--ip", + dest = "esp_ip", + action = "store", + help = "ESP8266 IP Address.", + default = False + ) + group.add_option("-I", "--host_ip", + dest = "host_ip", + action = "store", + help = "Host IP Address.", + default = "0.0.0.0" + ) + group.add_option("-p", "--port", + dest = "esp_port", + type = "int", + help = "ESP8266 ota Port. Default 8266", + default = 8266 + ) + group.add_option("-P", "--host_port", + dest = "host_port", + type = "int", + help = "Host server ota Port. Default random 10000-60000", + default = random.randint(10000,60000) + ) + parser.add_option_group(group) + + # auth + group = optparse.OptionGroup(parser, "Authentication") + group.add_option("-a", "--auth", + dest = "auth", + help = "Set authentication password.", + action = "store", + default = "" + ) + parser.add_option_group(group) + + # image + group = optparse.OptionGroup(parser, "Image") + group.add_option("-f", "--file", + dest = "image", + help = "Image file.", + metavar="FILE", + default = None + ) + group.add_option("-s", "--spiffs", + dest = "spiffs", + action = "store_true", + help = "Use this option to transmit a SPIFFS image and do not flash the module.", + default = False + ) + parser.add_option_group(group) + + # output group + group = optparse.OptionGroup(parser, "Output") + group.add_option("-d", "--debug", + dest = "debug", + help = "Show debug output. And override loglevel with debug.", + action = "store_true", + default = False + ) + group.add_option("-r", "--progress", + dest = "progress", + help = "Show progress output. Does not work for ArduinoIDE", + action = "store_true", + default = False + ) + parser.add_option_group(group) + + (options, args) = parser.parse_args(unparsed_args) + + return options +# end parser + + +def main(args): + options = parser(args) + loglevel = logging.WARNING + if (options.debug): + loglevel = logging.DEBUG + + logging.basicConfig(level = loglevel, format = '%(asctime)-8s [%(levelname)s]: %(message)s', datefmt = '%H:%M:%S') + logging.debug("Options: %s", str(options)) + + # check options + global PROGRESS + PROGRESS = options.progress + if (not options.esp_ip or not options.image): + logging.critical("Not enough arguments.") + return 1 + + command = FLASH + if (options.spiffs): + command = SPIFFS + + return serve(options.esp_ip, options.host_ip, options.esp_port, options.host_port, options.auth, options.image, command) +# end main + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/tools/gen_esp32part.exe b/tools/gen_esp32part.exe index 6123e17c622..11764a5540c 100644 Binary files a/tools/gen_esp32part.exe and b/tools/gen_esp32part.exe differ diff --git a/tools/platformio-build.py b/tools/platformio-build.py index 165f0ea73d1..b321c9c9360 100644 --- a/tools/platformio-build.py +++ b/tools/platformio-build.py @@ -51,6 +51,7 @@ CPPPATH=[ join(FRAMEWORK_DIR, "tools", "sdk", "include", "config"), join(FRAMEWORK_DIR, "tools", "sdk", "include", "bluedroid"), + join(FRAMEWORK_DIR, "tools", "sdk", "include", "bootloader_support"), join(FRAMEWORK_DIR, "tools", "sdk", "include", "bt"), join(FRAMEWORK_DIR, "tools", "sdk", "include", "driver"), join(FRAMEWORK_DIR, "tools", "sdk", "include", "esp32"), diff --git a/tools/sdk/include/bootloader_support/esp_efuse.h b/tools/sdk/include/bootloader_support/esp_efuse.h new file mode 100644 index 00000000000..41588396c42 --- /dev/null +++ b/tools/sdk/include/bootloader_support/esp_efuse.h @@ -0,0 +1,56 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef _ESP_EFUSE_H +#define _ESP_EFUSE_H + +#include "soc/efuse_reg.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* @brief Permanently update values written to the efuse write registers + * + * After updating EFUSE_BLKx_WDATAx_REG registers with new values to + * write, call this function to permanently write them to efuse. + * + * @note Setting bits in efuse is permanent, they cannot be unset. + * + * @note Due to this restriction you don't need to copy values to + * Efuse write registers from the matching read registers, bits which + * are set in the read register but unset in the matching write + * register will be unchanged when new values are burned. + * + * @note This function is not threadsafe, if calling code updates + * efuse values from multiple tasks then this is caller's + * responsibility to serialise. + * + * After burning new efuses, the read registers are updated to match + * the new efuse values. + */ +void esp_efuse_burn_new_values(void); + +/* @brief Reset efuse write registers + * + * Efuse write registers are written to zero, to negate + * any changes that have been staged here. + */ +void esp_efuse_reset(void); + +#ifdef __cplusplus +} +#endif + +#endif /* __ESP_EFUSE_H */ + diff --git a/tools/sdk/include/bootloader_support/esp_flash_encrypt.h b/tools/sdk/include/bootloader_support/esp_flash_encrypt.h new file mode 100644 index 00000000000..ba370644a46 --- /dev/null +++ b/tools/sdk/include/bootloader_support/esp_flash_encrypt.h @@ -0,0 +1,102 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef __ESP32_FLASH_ENCRYPT_H +#define __ESP32_FLASH_ENCRYPT_H + +#include +#include "esp_attr.h" +#include "esp_err.h" +#include "esp_spi_flash.h" +#include "soc/efuse_reg.h" + +/** + * @file esp_partition.h + * @brief Support functions for flash encryption features + * + * Can be compiled as part of app or bootloader code. + */ + +/** @brief Is flash encryption currently enabled in hardware? + * + * Flash encryption is enabled if the FLASH_CRYPT_CNT efuse has an odd number of bits set. + * + * @return true if flash encryption is enabled. + */ +static inline /** @cond */ IRAM_ATTR /** @endcond */ bool esp_flash_encryption_enabled(void) { + uint32_t flash_crypt_cnt = REG_GET_FIELD(EFUSE_BLK0_RDATA0_REG, EFUSE_RD_FLASH_CRYPT_CNT); + /* __builtin_parity is in flash, so we calculate parity inline */ + bool enabled = false; + while(flash_crypt_cnt) { + if (flash_crypt_cnt & 1) { + enabled = !enabled; + } + flash_crypt_cnt >>= 1; + } + return enabled; +} + +/* @brief Update on-device flash encryption + * + * Intended to be called as part of the bootloader process if flash + * encryption is enabled in device menuconfig. + * + * If FLASH_CRYPT_CNT efuse parity is 1 (ie odd number of bits set), + * then return ESP_OK immediately (indicating flash encryption is enabled + * and functional). + * + * If FLASH_CRYPT_CNT efuse parity is 0 (ie even number of bits set), + * assume the flash has just been written with plaintext that needs encrypting. + * + * The following regions of flash are encrypted in place: + * + * - The bootloader image, if a valid plaintext image is found.[*] + * - The partition table, if a valid plaintext table is found. + * - Any app partition that contains a valid plaintext app image. + * - Any other partitions with the "encrypt" flag set. [**] + * + * After the re-encryption process completes, a '1' bit is added to the + * FLASH_CRYPT_CNT value (setting the parity to 1) and the EFUSE is re-burned. + * + * [*] If reflashing bootloader with secure boot enabled, pre-encrypt + * the bootloader before writing it to flash or secure boot will fail. + * + * [**] For this reason, if serial re-flashing a previous flashed + * device with secure boot enabled and using FLASH_CRYPT_CNT to + * trigger re-encryption, you must simultaneously re-flash plaintext + * content to all partitions with the "encrypt" flag set or this + * data will be corrupted (encrypted twice). + * + * @note The post-condition of this function is that all + * partitions that should be encrypted are encrypted. + * + * @note Take care not to power off the device while this function + * is running, or the partition currently being encrypted will be lost. + * + * @return ESP_OK if all operations succeeded, ESP_ERR_INVALID_STATE + * if a fatal error occured during encryption of all partitions. + */ +esp_err_t esp_flash_encrypt_check_and_update(void); + + +/** @brief Encrypt-in-place a block of flash sectors + * + * @param src_addr Source offset in flash. Should be multiple of 4096 bytes. + * @param data_length Length of data to encrypt in bytes. Will be rounded up to next multiple of 4096 bytes. + * + * @return ESP_OK if all operations succeeded, ESP_ERR_FLASH_OP_FAIL + * if SPI flash fails, ESP_ERR_FLASH_OP_TIMEOUT if flash times out. + */ +esp_err_t esp_flash_encrypt_region(uint32_t src_addr, size_t data_length); + +#endif diff --git a/tools/sdk/include/bootloader_support/esp_flash_partitions.h b/tools/sdk/include/bootloader_support/esp_flash_partitions.h new file mode 100644 index 00000000000..63ee8221260 --- /dev/null +++ b/tools/sdk/include/bootloader_support/esp_flash_partitions.h @@ -0,0 +1,39 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef __ESP_FLASH_PARTITIONS_H +#define __ESP_FLASH_PARTITIONS_H + +#include "esp_err.h" +#include "esp_flash_data_types.h" +#include + +/* Pre-partition table fixed flash offsets */ +#define ESP_BOOTLOADER_DIGEST_OFFSET 0x0 +#define ESP_BOOTLOADER_OFFSET 0x1000 /* Offset of bootloader image. Has matching value in bootloader KConfig.projbuild file. */ +#define ESP_PARTITION_TABLE_OFFSET 0x8000 /* Offset of partition table. Has matching value in partition_table Kconfig.projbuild file. */ + +#define ESP_PARTITION_TABLE_MAX_LEN 0xC00 /* Maximum length of partition table data */ +#define ESP_PARTITION_TABLE_MAX_ENTRIES (ESP_PARTITION_TABLE_MAX_LEN / sizeof(esp_partition_info_t)) /* Maximum length of partition table data, including terminating entry */ + +/* @brief Verify the partition table (does not include verifying secure boot cryptographic signature) + * + * @param partition_table Pointer to at least ESP_PARTITION_TABLE_MAX_ENTRIES of potential partition table data. (ESP_PARTITION_TABLE_MAX_LEN bytes.) + * @param log_errors Log errors if the partition table is invalid. + * @param num_partitions If result is ESP_OK, num_partitions is updated with total number of partitions (not including terminating entry). + * + * @return ESP_OK on success, ESP_ERR_INVALID_STATE if partition table is not valid. + */ +esp_err_t esp_partition_table_basic_verify(const esp_partition_info_t *partition_table, bool log_errors, int *num_partitions); + +#endif diff --git a/tools/sdk/include/bootloader_support/esp_image_format.h b/tools/sdk/include/bootloader_support/esp_image_format.h new file mode 100644 index 00000000000..e0759c3c0e4 --- /dev/null +++ b/tools/sdk/include/bootloader_support/esp_image_format.h @@ -0,0 +1,143 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef __ESP32_IMAGE_FORMAT_H +#define __ESP32_IMAGE_FORMAT_H + +#include +#include + +#define ESP_ERR_IMAGE_BASE 0x2000 +#define ESP_ERR_IMAGE_FLASH_FAIL (ESP_ERR_IMAGE_BASE + 1) +#define ESP_ERR_IMAGE_INVALID (ESP_ERR_IMAGE_BASE + 2) + +/* Support for app/bootloader image parsing + Can be compiled as part of app or bootloader code. +*/ + +/* SPI flash mode, used in esp_image_header_t */ +typedef enum { + ESP_IMAGE_SPI_MODE_QIO, + ESP_IMAGE_SPI_MODE_QOUT, + ESP_IMAGE_SPI_MODE_DIO, + ESP_IMAGE_SPI_MODE_DOUT, + ESP_IMAGE_SPI_MODE_FAST_READ, + ESP_IMAGE_SPI_MODE_SLOW_READ +} esp_image_spi_mode_t; + +/* SPI flash clock frequency */ +enum { + ESP_IMAGE_SPI_SPEED_40M, + ESP_IMAGE_SPI_SPEED_26M, + ESP_IMAGE_SPI_SPEED_20M, + ESP_IMAGE_SPI_SPEED_80M = 0xF +} esp_image_spi_freq_t; + +/* Supported SPI flash sizes */ +typedef enum { + ESP_IMAGE_FLASH_SIZE_1MB = 0, + ESP_IMAGE_FLASH_SIZE_2MB, + ESP_IMAGE_FLASH_SIZE_4MB, + ESP_IMAGE_FLASH_SIZE_8MB, + ESP_IMAGE_FLASH_SIZE_16MB, + ESP_IMAGE_FLASH_SIZE_MAX +} esp_image_flash_size_t; + +#define ESP_IMAGE_HEADER_MAGIC 0xE9 + +/* Main header of binary image */ +typedef struct { + uint8_t magic; + uint8_t segment_count; + uint8_t spi_mode; /* flash read mode (esp_image_spi_mode_t as uint8_t) */ + uint8_t spi_speed: 4; /* flash frequency (esp_image_spi_freq_t as uint8_t) */ + uint8_t spi_size: 4; /* flash chip size (esp_image_flash_size_t as uint8_t) */ + uint32_t entry_addr; + uint8_t encrypt_flag; /* encrypt flag */ + uint8_t extra_header[15]; /* ESP32 additional header, unused by second bootloader */ +} esp_image_header_t; + +/* Header of binary image segment */ +typedef struct { + uint32_t load_addr; + uint32_t data_len; +} esp_image_segment_header_t; + + +/** + * @brief Read an ESP image header from flash. + * + * If encryption is enabled, data will be transparently decrypted. + * + * @param src_addr Address in flash to load image header. Must be 4 byte aligned. + * @param log_errors Log error output if image header appears invalid. + * @param[out] image_header Pointer to an esp_image_header_t struture to be filled with data. If the function fails, contents are undefined. + * + * @return ESP_OK if image header was loaded, ESP_ERR_IMAGE_FLASH_FAIL + * if a SPI flash error occurs, ESP_ERR_IMAGE_INVALID if the image header + * appears invalid. + */ +esp_err_t esp_image_load_header(uint32_t src_addr, bool log_errors, esp_image_header_t *image_header); + +/** + * @brief Read the segment header and data offset of a segment in the image. + * + * If encryption is enabled, data will be transparently decrypted. + * + * @param index Index of the segment to load information for. + * @param src_addr Base address in flash of the image. + * @param[in] image_header Pointer to the flash image header, already loaded by @ref esp_image_load_header(). + * @param log_errors Log errors reading the segment header. + * @param[out] segment_header Pointer to a segment header structure to be filled with data. If the function fails, contents are undefined. + * @param[out] segment_data_offset Pointer to the data offset of the segment. + * + * @return ESP_OK if segment_header & segment_data_offset were loaded successfully, ESP_ERR_IMAGE_FLASH_FAIL if a SPI flash error occurs, ESP_ERR_IMAGE_INVALID if the image header appears invalid, ESP_ERR_INVALID_ARG if the index is invalid. + */ +esp_err_t esp_image_load_segment_header(uint8_t index, uint32_t src_addr, const esp_image_header_t *image_header, bool log_errors, esp_image_segment_header_t *segment_header, uint32_t *segment_data_offset); + + +/** + * @brief Non-cryptographically validate app image integrity. On success, length of image is provided to caller. + * + * If the image has a secure boot signature appended, the signature is not checked and this length is not included in the + * output value. + * + * Image validation checks: + * - Magic byte + * - No single segment longer than 16MB + * - Total image no longer than 16MB + * - 8 bit image checksum is valid + * + * If flash encryption is enabled, the image will be tranpsarently decrypted. + * + * @param src_addr Offset of the start of the image in flash. Must be 4 byte aligned. + * @param allow_decrypt If true and flash encryption is enabled, the image will be transparently decrypted. + * @param log_errors Log errors verifying the image. + * @param[out] length Length of the image, set to a value if the image is valid. Can be null. + * + * @return ESP_OK if image is valid, ESP_FAIL or ESP_ERR_IMAGE_INVALID on errors. + * + */ +esp_err_t esp_image_basic_verify(uint32_t src_addr, bool log_errors, uint32_t *length); + + +typedef struct { + uint32_t drom_addr; + uint32_t drom_load_addr; + uint32_t drom_size; + uint32_t irom_addr; + uint32_t irom_load_addr; + uint32_t irom_size; +} esp_image_flash_mapping_t; + +#endif diff --git a/tools/sdk/include/bootloader_support/esp_secure_boot.h b/tools/sdk/include/bootloader_support/esp_secure_boot.h new file mode 100644 index 00000000000..8e33a8b4609 --- /dev/null +++ b/tools/sdk/include/bootloader_support/esp_secure_boot.h @@ -0,0 +1,91 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef __ESP32_SECUREBOOT_H +#define __ESP32_SECUREBOOT_H + +#include +#include +#include "soc/efuse_reg.h" + +/* Support functions for secure boot features. + + Can be compiled as part of app or bootloader code. +*/ + +/** @brief Is secure boot currently enabled in hardware? + * + * Secure boot is enabled if the ABS_DONE_0 efuse is blown. This means + * that the ROM bootloader code will only boot a verified secure + * bootloader digest from now on. + * + * @return true if secure boot is enabled. + */ +static inline bool esp_secure_boot_enabled(void) { + return REG_READ(EFUSE_BLK0_RDATA6_REG) & EFUSE_RD_ABS_DONE_0; +} + + +/** @brief Enable secure boot if it is not already enabled. + * + * @important If this function succeeds, secure boot is permanently + * enabled on the chip via efuse. + * + * @important This function is intended to be called from bootloader code only. + * + * If secure boot is not yet enabled for bootloader, this will + * generate the secure boot digest and enable secure boot by blowing + * the EFUSE_RD_ABS_DONE_0 efuse. + * + * This function does not verify secure boot of the bootloader (the + * ROM bootloader does this.) + * + * Will fail if efuses have been part-burned in a way that indicates + * secure boot should not or could not be correctly enabled. + * + * + * @return ESP_ERR_INVALID_STATE if efuse state doesn't allow + * secure boot to be enabled cleanly. ESP_OK if secure boot + * is enabled on this chip from now on. + */ +esp_err_t esp_secure_boot_permanently_enable(void); + +/** @brief Verify the secure boot signature (determinstic ECDSA w/ SHA256) appended to some binary data in flash. + * + * Public key is compiled into the calling program. See docs/security/secure-boot.rst for details. + * + * @param src_addr Starting offset of the data in flash. + * @param length Length of data in bytes. Signature is appended -after- length bytes. + * + * If flash encryption is enabled, the image will be transparently decrypted while being verified. + * + * @return ESP_OK if signature is valid, ESP_ERR_INVALID_STATE if + * signature fails, ESP_FAIL for other failures (ie can't read flash). + */ +esp_err_t esp_secure_boot_verify_signature(uint32_t src_addr, uint32_t length); + +/** @brief Secure boot verification block, on-flash data format. */ +typedef struct { + uint32_t version; + uint8_t signature[64]; +} esp_secure_boot_sig_block_t; + +#define FLASH_OFFS_SECURE_BOOT_IV_DIGEST 0 + +/** @brief Secure boot IV+digest header */ +typedef struct { + uint8_t iv[128]; + uint8_t digest[64]; +} esp_secure_boot_iv_digest_t; + +#endif