From 5099d25965e426f77051781e835d08fec3918c03 Mon Sep 17 00:00:00 2001 From: Klaus K Wilting Date: Sun, 12 Aug 2018 00:17:57 +0200 Subject: [PATCH] testing --- lib/BintrayClient/library.json | 24 +++ lib/BintrayClient/src/BintrayCertificates.h | 102 +++++++++ lib/BintrayClient/src/BintrayClient.cpp | 149 +++++++++++++ lib/BintrayClient/src/BintrayClient.h | 49 +++++ platformio.ini | 46 +++- src/SecureOTA.cpp | 225 ++++++++++++++++++++ src/SecureOTA.h | 25 +++ src/globals.h | 1 + src/main.cpp | 4 +- src/rcommand.cpp | 29 ++- src/rcommand.h | 3 + 11 files changed, 648 insertions(+), 9 deletions(-) create mode 100644 lib/BintrayClient/library.json create mode 100644 lib/BintrayClient/src/BintrayCertificates.h create mode 100644 lib/BintrayClient/src/BintrayClient.cpp create mode 100644 lib/BintrayClient/src/BintrayClient.h create mode 100644 src/SecureOTA.cpp create mode 100644 src/SecureOTA.h diff --git a/lib/BintrayClient/library.json b/lib/BintrayClient/library.json new file mode 100644 index 00000000..677cec5c --- /dev/null +++ b/lib/BintrayClient/library.json @@ -0,0 +1,24 @@ +{ + "name": "BintrayClient", + "keywords": "bintray, ota, cdn, storage", + "description": "A BintrayClient to connect to a JFrog Bintray.", + "authors": [ + { + "name": "PlatformIO", + "url": "https://platformio.org/" + } + ], + "repository": { + "type": "git", + "url": "https://github.com/platformio/platformio-examples" + }, + "export": { + "include": "bintray-secure-ota/lib/BintrayClient" + }, + "dependencies": { + "ArduinoJson": "^5.13.1" + }, + "version": "1.0.0", + "frameworks": "arduino", + "platforms": "espressif32" +} diff --git a/lib/BintrayClient/src/BintrayCertificates.h b/lib/BintrayClient/src/BintrayCertificates.h new file mode 100644 index 00000000..ec5fdaa3 --- /dev/null +++ b/lib/BintrayClient/src/BintrayCertificates.h @@ -0,0 +1,102 @@ +/* + Copyright (c) 2014-present PlatformIO + + 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 BINTRAY_CERTIFICATES_H +#define BINTRAY_CERTIFICATES_H + +const char* BINTRAY_API_ROOT_CA = \ +"-----BEGIN CERTIFICATE-----\n" \ +"MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT\n" \ +"MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i\n" \ +"YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG\n" \ +"EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg\n" \ +"R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9\n" \ +"9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq\n" \ +"fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv\n" \ +"iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU\n" \ +"1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+\n" \ +"bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW\n" \ +"MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA\n" \ +"ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l\n" \ +"uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn\n" \ +"Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS\n" \ +"tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF\n" \ +"PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un\n" \ +"hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV\n" \ +"5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw==\n" \ +"-----END CERTIFICATE-----\n"; + +const char* BINTRAY_AKAMAI_ROOT_CA = \ +"-----BEGIN CERTIFICATE-----\n"\ +"MIIElDCCA3ygAwIBAgIQAf2j627KdciIQ4tyS8+8kTANBgkqhkiG9w0BAQsFADBh\n"\ +"MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n"\ +"d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\n"\ +"QTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaME0xCzAJBgNVBAYTAlVT\n"\ +"MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJzAlBgNVBAMTHkRpZ2lDZXJ0IFNIQTIg\n"\ +"U2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\n"\ +"ANyuWJBNwcQwFZA1W248ghX1LFy949v/cUP6ZCWA1O4Yok3wZtAKc24RmDYXZK83\n"\ +"nf36QYSvx6+M/hpzTc8zl5CilodTgyu5pnVILR1WN3vaMTIa16yrBvSqXUu3R0bd\n"\ +"KpPDkC55gIDvEwRqFDu1m5K+wgdlTvza/P96rtxcflUxDOg5B6TXvi/TC2rSsd9f\n"\ +"/ld0Uzs1gN2ujkSYs58O09rg1/RrKatEp0tYhG2SS4HD2nOLEpdIkARFdRrdNzGX\n"\ +"kujNVA075ME/OV4uuPNcfhCOhkEAjUVmR7ChZc6gqikJTvOX6+guqw9ypzAO+sf0\n"\ +"/RR3w6RbKFfCs/mC/bdFWJsCAwEAAaOCAVowggFWMBIGA1UdEwEB/wQIMAYBAf8C\n"\ +"AQAwDgYDVR0PAQH/BAQDAgGGMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYY\n"\ +"aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6\n"\ +"Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RDQS5jcmwwN6A1\n"\ +"oDOGMWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RD\n"\ +"QS5jcmwwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v\n"\ +"d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwHQYDVR0OBBYEFA+AYRyCMWHVLyjnjUY4tCzh\n"\ +"xtniMB8GA1UdIwQYMBaAFAPeUDVW0Uy7ZvCj4hsbw5eyPdFVMA0GCSqGSIb3DQEB\n"\ +"CwUAA4IBAQAjPt9L0jFCpbZ+QlwaRMxp0Wi0XUvgBCFsS+JtzLHgl4+mUwnNqipl\n"\ +"5TlPHoOlblyYoiQm5vuh7ZPHLgLGTUq/sELfeNqzqPlt/yGFUzZgTHbO7Djc1lGA\n"\ +"8MXW5dRNJ2Srm8c+cftIl7gzbckTB+6WohsYFfZcTEDts8Ls/3HB40f/1LkAtDdC\n"\ +"2iDJ6m6K7hQGrn2iWZiIqBtvLfTyyRRfJs8sjX7tN8Cp1Tm5gr8ZDOo0rwAhaPit\n"\ +"c+LJMto4JQtV05od8GiG7S5BNO98pVAdvzr508EIDObtHopYJeS4d60tbvVS3bR0\n"\ +"j6tJLp07kzQoH3jOlOrHvdPJbRzeXDLz\n"\ +"-----END CERTIFICATE-----\n"; + +const char* CLOUDFRONT_API_ROOT_CA = \ +"-----BEGIN CERTIFICATE-----\n"\ +"MIIE3zCCA8egAwIBAgIQYxgNOPuAl3ip0DWjFhj4QDANBgkqhkiG9w0BAQsFADCB\n"\ +"yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL\n"\ +"ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp\n"\ +"U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW\n"\ +"ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0\n"\ +"aG9yaXR5IC0gRzUwHhcNMTcxMTA2MDAwMDAwWhcNMjIxMTA1MjM1OTU5WjBhMQsw\n"\ +"CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu\n"\ +"ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMjCC\n"\ +"ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALs3zTTce2vJsmiQrUp1/0a6\n"\ +"IQoIjfUZVMn7iNvzrvI6iZE8euarBhprz6wt6F4JJES6Ypp+1qOofuBUdSAFrFC3\n"\ +"nGMabDDc2h8Zsdce3v3X4MuUgzeu7B9DTt17LNK9LqUv5Km4rTrUmaS2JembawBg\n"\ +"kmD/TyFJGPdnkKthBpyP8rrptOmSMmu181foXRvNjB2rlQSVSfM1LZbjSW3dd+P7\n"\ +"SUu0rFUHqY+Vs7Qju0xtRfD2qbKVMLT9TFWMJ0pXFHyCnc1zktMWSgYMjFDRjx4J\n"\ +"vheh5iHK/YPlELyDpQrEZyj2cxQUPUZ2w4cUiSE0Ta8PRQymSaG6u5zFsTODKYUC\n"\ +"AwEAAaOCAScwggEjMB0GA1UdDgQWBBROIlQgGJXm427mD/r6uRLtBhePOTAPBgNV\n"\ +"HRMBAf8EBTADAQH/MF8GA1UdIARYMFYwVAYEVR0gADBMMCMGCCsGAQUFBwIBFhdo\n"\ +"dHRwczovL2Quc3ltY2IuY29tL2NwczAlBggrBgEFBQcCAjAZDBdodHRwczovL2Qu\n"\ +"c3ltY2IuY29tL3JwYTAvBgNVHR8EKDAmMCSgIqAghh5odHRwOi8vcy5zeW1jYi5j\n"\ +"b20vcGNhMy1nNS5jcmwwDgYDVR0PAQH/BAQDAgGGMC4GCCsGAQUFBwEBBCIwIDAe\n"\ +"BggrBgEFBQcwAYYSaHR0cDovL3Muc3ltY2QuY29tMB8GA1UdIwQYMBaAFH/TZafC\n"\ +"3ey78DAJ80M5+gKvMzEzMA0GCSqGSIb3DQEBCwUAA4IBAQBQ3dNWKSUBip6n5X1N\n"\ +"ua8bjKLSJzXlnescavPECMpFBlIIKH2mc6mL2Xr/wkSIBDrsqAO3sBcmoJN+n8V3\n"\ +"0O5JelrtEAFYSyRDXfu78ZlHn6kvV5/jPUFECEM/hdN0x8WdLpGjJMqfs0EG5qHj\n"\ +"+UaxpucWD445wea4zlK7hUR+MA8fq0Yd1HEKj4c8TcgaQIHMa4KHr448cQ69e3CP\n"\ +"ECRhRNg+RAKT2I7SlaVzLvaB/8yym2oMCEsoqiRT8dbXg35aKEYmmzn3O/mnB7bG\n"\ +"Ud/EUrkIf7FVamgYZd1fSzQeg1cHqf0ja6eHpvq2bTl+cWFHaq/84KlHe5Rh0Csm\n"\ +"pZzn\n"\ +"-----END CERTIFICATE-----\n"; + +#endif // BINTRAY_CERTIFICATES_H diff --git a/lib/BintrayClient/src/BintrayClient.cpp b/lib/BintrayClient/src/BintrayClient.cpp new file mode 100644 index 00000000..b0e7543f --- /dev/null +++ b/lib/BintrayClient/src/BintrayClient.cpp @@ -0,0 +1,149 @@ +/* + Copyright (c) 2014-present PlatformIO + + 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. +**/ + +#include +#include +#include + +#include "BintrayClient.h" +#include "BintrayCertificates.h" + +BintrayClient::BintrayClient(const String &user, const String &repository, const String &package) + : m_user(user), m_repo(repository), m_package(package), + m_storage_host("dl.bintray.com"), + m_api_host("api.bintray.com") +{ + m_certificates.emplace_back("cloudfront.net", CLOUDFRONT_API_ROOT_CA); + m_certificates.emplace_back("akamai.bintray.com", BINTRAY_AKAMAI_ROOT_CA); + m_certificates.emplace_back("bintray.com", BINTRAY_API_ROOT_CA); +} + +String BintrayClient::getUser() const +{ + return m_user; +} + +String BintrayClient::getRepository() const +{ + return m_repo; +} + +String BintrayClient::getPackage() const +{ + return m_package; +} + +String BintrayClient::getStorageHost() const +{ + return m_storage_host; +} + +String BintrayClient::getApiHost() const +{ + return m_api_host; +} + +String BintrayClient::getLatestVersionRequestUrl() const +{ + return String("https://") + getApiHost() + "/packages/" + getUser() + "/" + getRepository() + "/" + getPackage() + "/versions/_latest"; +} + +String BintrayClient::getBinaryRequestUrl(const String &version) const +{ + return String("https://") + getApiHost() + "/packages/" + getUser() + "/" + getRepository() + "/" + getPackage() + "/versions/" + version + "/files"; +} + +const char *BintrayClient::getCertificate(const String &url) const +{ + for(auto& cert: m_certificates) { + if(url.indexOf(cert.first) >= 0) { + return cert.second; + } + } + + // Return the certificate for *.bintray.com by default + return m_certificates.rbegin()->second; +} + +String BintrayClient::requestHTTPContent(const String &url) const +{ + String payload; + HTTPClient http; + http.begin(url, getCertificate(url)); + int httpCode = http.GET(); + + if (httpCode > 0) + { + if (httpCode == HTTP_CODE_OK) + { + payload = http.getString(); + } + } + else + { + Serial.printf("GET request failed, error: %s\n", http.errorToString(httpCode).c_str()); + } + + http.end(); + return payload; +} + +String BintrayClient::getLatestVersion() const +{ + String version; + const String url = getLatestVersionRequestUrl(); + String jsonResult = requestHTTPContent(url); + const size_t bufferSize = 1024; + if (jsonResult.length() > bufferSize) + { + Serial.println("Error: Could parse JSON. Input data is too big!"); + return version; + } + StaticJsonBuffer jsonBuffer; + + JsonObject &root = jsonBuffer.parseObject(jsonResult.c_str()); + // Check for errors in parsing + if (!root.success()) + { + Serial.println("Error: Could not parse JSON!"); + return version; + } + return root.get("name"); +} + +String BintrayClient::getBinaryPath(const String &version) const +{ + String path; + const String url = getBinaryRequestUrl(version); + String jsonResult = requestHTTPContent(url); + + const size_t bufferSize = 1024; + if (jsonResult.length() > bufferSize) + { + Serial.println("Error: Could parse JSON. Input data is too big!"); + return path; + } + StaticJsonBuffer jsonBuffer; + + JsonArray &root = jsonBuffer.parseArray(jsonResult.c_str()); + JsonObject &firstItem = root[0]; + if (!root.success()) + { //Check for errors in parsing + Serial.println("Error: Could not parse JSON!"); + return path; + } + return "/" + getUser() + "/" + getRepository() + "/" + firstItem.get("path"); +} diff --git a/lib/BintrayClient/src/BintrayClient.h b/lib/BintrayClient/src/BintrayClient.h new file mode 100644 index 00000000..9761c7c6 --- /dev/null +++ b/lib/BintrayClient/src/BintrayClient.h @@ -0,0 +1,49 @@ +/* + Copyright (c) 2014-present PlatformIO + + 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 BINTRAY_CLIENT_H +#define BINTRAY_CLIENT_H + +#include +#include +#include + +class BintrayClient { + +public: + BintrayClient(const String& user, const String& repository, const String& package); + String getUser() const; + String getRepository() const; + String getPackage() const; + String getStorageHost() const; + String getApiHost() const; + const char* getCertificate(const String& url) const; + String getLatestVersion() const; + String getBinaryPath(const String& version) const; + +private: + String requestHTTPContent(const String& url) const; + String getLatestVersionRequestUrl() const; + String getBinaryRequestUrl(const String& version) const; + String m_user; + String m_repo; + String m_package; + const String m_storage_host; + const String m_api_host; + std::vector> m_certificates; +}; + +#endif // BINTRAY_CLIENT_H diff --git a/platformio.ini b/platformio.ini index 98296872..91ee4e27 100644 --- a/platformio.ini +++ b/platformio.ini @@ -11,12 +11,12 @@ ; ---> SELECT TARGET PLATFORM HERE! <--- [platformio] -env_default = generic +;env_default = generic ;env_default = ebox ;env_default = heltec ;env_default = ttgov1 ;env_default = ttgov2 -;env_default = ttgov21 +env_default = ttgov21 ;env_default = ttgobeam ;env_default = lopy ;env_default = lopy4 @@ -27,10 +27,45 @@ env_default = generic ; description = Paxcounter is a proof-of-concept ESP32 device for metering passenger flows in realtime. It counts how many mobile devices are around. +[bintray] +user = cyberman54 +repository = paxcounter +package = esp32-paxcounter +api_token = 9f02e2a2374c278fd79d5bcf4b4442fca9752012 +;api_token = ${env.BINTRAY_API_TOKEN} + +; Wi-Fi network settings +[wifi] +ssid = PRENZLNET-G +password = 435Huse8!? +;ssid = ${env.PIO_WIFI_SSID} +;password = ${env.PIO_WIFI_PASSWORD} + +[common] +platform = https://github.com/platformio/platform-espressif32.git + +; firmware version, please modify it between releases +; positive integer value +release_version = 1 + +; build configuration based on Bintray and Wi-Fi settings +build_flags = + '-DWIFI_SSID="${wifi.ssid}"' + '-DWIFI_PASS="${wifi.password}"' + '-DBINTRAY_USER="${bintray.user}"' + '-DBINTRAY_REPO="${bintray.repository}"' + '-DBINTRAY_PACKAGE="${bintray.package}"' + '-DVERSION=0' + +; extra dependencies +lib_deps = ArduinoJson + + [common_env_data] platform_espressif32 = espressif32@1.2.0 ;platform_espressif32 = https://github.com/platformio/platform-espressif32.git#feature/stage -board_build.partitions = no_ota.csv +;board_build.partitions = no_ota.csv +board_build.partitions = min_spiffs.csv lib_deps_all = lib_deps_display = U8g2@>=2.23.12 @@ -48,9 +83,9 @@ build_flags = ; Error ; -DCORE_DEBUG_LEVEL=1 ; Warn - -DCORE_DEBUG_LEVEL=2 +; -DCORE_DEBUG_LEVEL=2 ; Info -; -DCORE_DEBUG_LEVEL=3 + -DCORE_DEBUG_LEVEL=3 ; Debug ; -DCORE_DEBUG_LEVEL=4 ; Verbose @@ -124,6 +159,7 @@ lib_deps = ${common_env_data.lib_deps_all} ${common_env_data.lib_deps_display} build_flags = + ${common.build_flags} ${common_env_data.build_flags} [env:ttgobeam] diff --git a/src/SecureOTA.cpp b/src/SecureOTA.cpp new file mode 100644 index 00000000..7496c19c --- /dev/null +++ b/src/SecureOTA.cpp @@ -0,0 +1,225 @@ +/* + Copyright (c) 2014-present PlatformIO + + 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. +**/ + +#include +#include +#include +#include "SecureOTA.h" + +const BintrayClient bintray(BINTRAY_USER, BINTRAY_REPO, BINTRAY_PACKAGE); + +// Connection port (HTTPS) +const int port = 443; + +// Connection timeout +const uint32_t RESPONSE_TIMEOUT_MS = 5000; + +// Variables to validate firmware content +volatile int contentLength = 0; +volatile bool isValidContentType = false; + +void checkFirmwareUpdates() +{ + // Fetch the latest firmware version + const String latest = bintray.getLatestVersion(); + if (latest.length() == 0) + { + Serial.println("Could not load info about the latest firmware, so nothing to update. Continue ..."); + return; + } + else if (atoi(latest.c_str()) <= VERSION) + { + //Serial.println("The current firmware is up to date. Continue ..."); + return; + } + + Serial.println("There is a new version of firmware available: v." + latest); + processOTAUpdate(latest); +} + +// A helper function to extract header value from header +inline String getHeaderValue(String header, String headerName) +{ + return header.substring(strlen(headerName.c_str())); +} + +/** + * OTA update processing + */ +void processOTAUpdate(const String &version) +{ + String firmwarePath = bintray.getBinaryPath(version); + if (!firmwarePath.endsWith(".bin")) + { + Serial.println("Unsupported binary format. OTA update cannot be performed!"); + return; + } + + String currentHost = bintray.getStorageHost(); + String prevHost = currentHost; + + WiFiClientSecure client; + client.setCACert(bintray.getCertificate(currentHost)); + + if (!client.connect(currentHost.c_str(), port)) + { + Serial.println("Cannot connect to " + currentHost); + return; + } + + bool redirect = true; + while (redirect) + { + if (currentHost != prevHost) + { + client.stop(); + client.setCACert(bintray.getCertificate(currentHost)); + if (!client.connect(currentHost.c_str(), port)) + { + Serial.println("Redirect detected! Cannot connect to " + currentHost + " for some reason!"); + return; + } + } + + //Serial.println("Requesting: " + firmwarePath); + + client.print(String("GET ") + firmwarePath + " HTTP/1.1\r\n"); + client.print(String("Host: ") + currentHost + "\r\n"); + client.print("Cache-Control: no-cache\r\n"); + client.print("Connection: close\r\n\r\n"); + + unsigned long timeout = millis(); + while (client.available() == 0) + { + if (millis() - timeout > RESPONSE_TIMEOUT_MS) + { + Serial.println("Client Timeout !"); + client.stop(); + return; + } + } + + while (client.available()) + { + String line = client.readStringUntil('\n'); + // Check if the line is end of headers by removing space symbol + line.trim(); + // if the the line is empty, this is the end of the headers + if (!line.length()) + { + break; // proceed to OTA update + } + + // Check allowed HTTP responses + if (line.startsWith("HTTP/1.1")) + { + if (line.indexOf("200") > 0) + { + //Serial.println("Got 200 status code from server. Proceeding to firmware flashing"); + redirect = false; + } + else if (line.indexOf("302") > 0) + { + //Serial.println("Got 302 status code from server. Redirecting to the new address"); + redirect = true; + } + else + { + //Serial.println("Could not get a valid firmware url"); + //Unexptected HTTP response. Retry or skip update? + redirect = false; + } + } + + // Extracting new redirect location + if (line.startsWith("Location: ")) + { + String newUrl = getHeaderValue(line, "Location: "); + //Serial.println("Got new url: " + newUrl); + newUrl.remove(0, newUrl.indexOf("//") + 2); + currentHost = newUrl.substring(0, newUrl.indexOf('/')); + newUrl.remove(newUrl.indexOf(currentHost), currentHost.length()); + firmwarePath = newUrl; + //Serial.println("firmwarePath: " + firmwarePath); + continue; + } + + // Checking headers + if (line.startsWith("Content-Length: ")) + { + contentLength = atoi((getHeaderValue(line, "Content-Length: ")).c_str()); + Serial.println("Got " + String(contentLength) + " bytes from server"); + } + + if (line.startsWith("Content-Type: ")) + { + String contentType = getHeaderValue(line, "Content-Type: "); + //Serial.println("Got " + contentType + " payload."); + if (contentType == "application/octet-stream") + { + isValidContentType = true; + } + } + } + } + + // check whether we have everything for OTA update + if (contentLength && isValidContentType) + { + if (Update.begin(contentLength)) + { + Serial.println("Starting Over-The-Air update. This may take some time to complete ..."); + size_t written = Update.writeStream(client); + + if (written == contentLength) + { + Serial.println("Written : " + String(written) + " successfully"); + } + else + { + Serial.println("Written only : " + String(written) + "/" + String(contentLength) + ". Retry?"); + // Retry?? + } + + if (Update.end()) + { + if (Update.isFinished()) + { + Serial.println("OTA update has successfully completed. Rebooting ..."); + ESP.restart(); + } + else + { + Serial.println("Something went wrong! OTA update hasn't been finished properly."); + } + } + else + { + Serial.println("An error Occurred. Error #: " + String(Update.getError())); + } + } + else + { + Serial.println("There isn't enough space to start OTA update"); + client.flush(); + } + } + else + { + Serial.println("There was no valid content in the response from the OTA server!"); + client.flush(); + } +} \ No newline at end of file diff --git a/src/SecureOTA.h b/src/SecureOTA.h new file mode 100644 index 00000000..d8b17c3f --- /dev/null +++ b/src/SecureOTA.h @@ -0,0 +1,25 @@ +/* + Copyright (c) 2014-present PlatformIO + + 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 SECURE_OTA_H +#define SECURE_OTA_H + +#include + +void checkFirmwareUpdates(); +void processOTAUpdate(const String &version); + +#endif // SECURE_OTA_H \ No newline at end of file diff --git a/src/globals.h b/src/globals.h index a34851a7..1043b13a 100644 --- a/src/globals.h +++ b/src/globals.h @@ -52,6 +52,7 @@ extern portMUX_TYPE timerMux; extern volatile int SendCycleTimerIRQ, HomeCycleIRQ, DisplayTimerIRQ, ChannelTimerIRQ, ButtonPressedIRQ; extern QueueHandle_t LoraSendQueue, SPISendQueue; +extern TaskHandle_t WifiLoopTask; extern std::array::iterator it; extern std::array beacons; diff --git a/src/main.cpp b/src/main.cpp index 1b258f14..b3825cb6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -41,6 +41,8 @@ hw_timer_t *channelSwitch = NULL, *displaytimer = NULL, *sendCycle = NULL, volatile int ButtonPressedIRQ = 0, ChannelTimerIRQ = 0, SendCycleTimerIRQ = 0, DisplayTimerIRQ = 0, HomeCycleIRQ = 0; +TaskHandle_t WifiLoopTask = NULL; + // RTos send queues for payload transmit #ifdef HAS_LORA QueueHandle_t LoraSendQueue; @@ -298,7 +300,7 @@ void setup() { // gets it's seed from RF noise reset_salt(); // get new 16bit for salting hashes xTaskCreatePinnedToCore(wifi_channel_loop, "wifiloop", 2048, (void *)1, 1, - NULL, 0); + &WifiLoopTask, 0); } // setup() /* end Arduino SETUP diff --git a/src/rcommand.cpp b/src/rcommand.cpp index 8899a805..70ac4c8e 100644 --- a/src/rcommand.cpp +++ b/src/rcommand.cpp @@ -138,7 +138,7 @@ void set_loraadr(uint8_t val[]) { ESP_LOGI(TAG, "Remote command: set LoRa ADR mode to %s", val[0] ? "on" : "off"); cfg.adrmode = val[0] ? 1 : 0; -LMIC_setAdrMode(cfg.adrmode); + LMIC_setAdrMode(cfg.adrmode); #else ESP_LOGW(TAG, "Remote command: LoRa not implemented"); #endif // HAS_LORA @@ -219,6 +219,29 @@ void get_gps(uint8_t val[]) { #endif }; +void set_update(uint8_t val[]) { + ESP_LOGI(TAG, "Remote command: get firmware update"); + + ESP_LOGI(TAG, "Stopping Wifi task on core 0"); + vTaskDelete(WifiLoopTask); + + ESP_LOGI(TAG, "Connecting to %s", WIFI_SSID); + ESP_ERROR_CHECK(esp_wifi_set_promiscuous(false)); // switch off monitor mode + //tcpipInit(); + tcpip_adapter_init(); + WiFi.mode(WIFI_STA); + + WiFi.begin(WIFI_SSID, WIFI_PASS); + + while (WiFi.status() != WL_CONNECTED) { + ESP_LOGI(TAG, "."); + delay(500); + } + + ESP_LOGI(TAG, "connected!"); + checkFirmwareUpdates(); +}; + // assign previously defined functions to set of numeric remote commands // format: opcode, function, #bytes params, // flag (1 = do make settings persistent / 0 = don't) @@ -233,8 +256,8 @@ cmd_t table[] = { {0x0d, set_vendorfilter, 1, false}, {0x0e, set_blescan, 1, true}, {0x0f, set_wifiant, 1, true}, {0x10, set_rgblum, 1, true}, {0x11, set_monitor, 1, true}, {0x12, set_beacon, 7, false}, - {0x80, get_config, 0, false}, {0x81, get_status, 0, false}, - {0x84, get_gps, 0, false}}; + {0x20, set_update, 0, false}, {0x80, get_config, 0, false}, + {0x81, get_status, 0, false}, {0x84, get_gps, 0, false}}; const uint8_t cmdtablesize = sizeof(table) / sizeof(table[0]); // number of commands in command table diff --git a/src/rcommand.h b/src/rcommand.h index 6afb905f..2d1902a1 100644 --- a/src/rcommand.h +++ b/src/rcommand.h @@ -6,6 +6,9 @@ #include "lorawan.h" #include "macsniff.h" +#include +#include "SecureOTA.h" + // table of remote commands and assigned functions typedef struct { const uint8_t opcode;