Fork of the espurna firmware for `mhsw` switches
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

300 lines
8.6 KiB

  1. /*
  2. HTTP(s) OTA MODULE
  3. Copyright (C) 2019 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
  4. */
  5. // -----------------------------------------------------------------------------
  6. // OTA by using Core's HTTP(s) updater
  7. // -----------------------------------------------------------------------------
  8. #if OTA_CLIENT == OTA_CLIENT_HTTPUPDATE
  9. #include <memory>
  10. #include <ESP8266HTTPClient.h>
  11. #include <ESP8266httpUpdate.h>
  12. #include <Schedule.h>
  13. #include "libs/URL.h"
  14. #include "libs/TypeChecks.h"
  15. #include "libs/SecureClientHelpers.h"
  16. #if SECURE_CLIENT != SECURE_CLIENT_NONE
  17. #if OTA_SECURE_CLIENT_INCLUDE_CA
  18. #include "static/ota_client_trusted_root_ca.h"
  19. #else
  20. #include "static/digicert_evroot_pem.h"
  21. #define _ota_client_trusted_root_ca _ssl_digicert_ev_root_ca
  22. #endif
  23. #endif // SECURE_CLIENT != SECURE_CLIENT_NONE
  24. // -----------------------------------------------------------------------------
  25. // Configuration templates
  26. // -----------------------------------------------------------------------------
  27. template <typename T>
  28. void _otaFollowRedirects(const std::true_type&, T& instance) {
  29. instance.followRedirects(true);
  30. }
  31. template <typename T>
  32. void _otaFollowRedirects(const std::false_type&, T& instance) {
  33. }
  34. template <typename T>
  35. t_httpUpdate_return _otaClientUpdate(const std::true_type&, T& instance, WiFiClient* client, const String& url) {
  36. return instance.update(*client, url);
  37. }
  38. template <typename T>
  39. t_httpUpdate_return _otaClientUpdate(const std::false_type&, T& instance, WiFiClient*, const String& url) {
  40. return instance.update(url);
  41. }
  42. namespace ota {
  43. template <typename T>
  44. using has_followRedirects_t = decltype(std::declval<T>().followRedirects(std::declval<bool>()));
  45. template <typename T>
  46. using has_followRedirects = is_detected<has_followRedirects_t, T>;
  47. template <typename T>
  48. using has_WiFiClient_argument_t = decltype(std::declval<T>().update(std::declval<WiFiClient&>(), std::declval<const String&>()));
  49. template <typename T>
  50. using has_WiFiClient_argument = is_detected<has_WiFiClient_argument_t, T>;
  51. }
  52. // -----------------------------------------------------------------------------
  53. // Settings helper
  54. // -----------------------------------------------------------------------------
  55. #if SECURE_CLIENT == SECURE_CLIENT_AXTLS
  56. SecureClientConfig _ota_sc_config {
  57. "OTA",
  58. []() -> String {
  59. return String(); // NOTE: unused
  60. },
  61. []() -> int {
  62. return getSetting("otaScCheck", OTA_SECURE_CLIENT_CHECK).toInt();
  63. },
  64. []() -> String {
  65. return getSetting("otaFP", OTA_FINGERPRINT);
  66. },
  67. true
  68. };
  69. #endif
  70. #if SECURE_CLIENT == SECURE_CLIENT_BEARSSL
  71. SecureClientConfig _ota_sc_config {
  72. "OTA",
  73. []() -> int {
  74. return getSetting("otaScCheck", OTA_SECURE_CLIENT_CHECK).toInt();
  75. },
  76. []() -> PGM_P {
  77. return _ota_client_trusted_root_ca;
  78. },
  79. []() -> String {
  80. return getSetting("otaFP", OTA_FINGERPRINT);
  81. },
  82. []() -> uint16_t {
  83. return getSetting("otaScMFLN", OTA_SECURE_CLIENT_MFLN).toInt();
  84. },
  85. true
  86. };
  87. #endif
  88. // -----------------------------------------------------------------------------
  89. // Generic update methods
  90. // -----------------------------------------------------------------------------
  91. void _otaClientRunUpdater(WiFiClient* client, const String& url, const String& fp = "") {
  92. UNUSED(client);
  93. UNUSED(fp);
  94. // Disabling EEPROM rotation to prevent writing to EEPROM after the upgrade
  95. eepromRotate(false);
  96. DEBUG_MSG_P(PSTR("[OTA] Downloading %s ...\n"), url.c_str());
  97. // TODO: support currentVersion (string arg after 'url')
  98. // NOTE: ESPhttpUpdate.update(..., fp) will **always** fail with empty fingerprint
  99. _otaFollowRedirects(ota::has_followRedirects<decltype(ESPhttpUpdate)>{}, ESPhttpUpdate);
  100. ESPhttpUpdate.rebootOnUpdate(false);
  101. t_httpUpdate_return result = HTTP_UPDATE_NO_UPDATES;
  102. // We expect both .update(url, "", String_fp) and .update(url) to survice until axTLS is removed from the Core
  103. #if (SECURE_CLIENT == SECURE_CLIENT_AXTLS)
  104. if (url.startsWith("https://")) {
  105. result = ESPhttpUpdate.update(url, "", fp);
  106. } else {
  107. result = ESPhttpUpdate.update(url);
  108. }
  109. #else
  110. // TODO: implement through callbacks?
  111. // see https://github.com/esp8266/Arduino/pull/6796
  112. result = _otaClientUpdate(ota::has_WiFiClient_argument<decltype(ESPhttpUpdate)>{}, ESPhttpUpdate, client, url);
  113. #endif
  114. switch (result) {
  115. case HTTP_UPDATE_FAILED:
  116. DEBUG_MSG_P(PSTR("[OTA] Update failed (error %d): %s\n"), ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str());
  117. eepromRotate(true);
  118. break;
  119. case HTTP_UPDATE_NO_UPDATES:
  120. DEBUG_MSG_P(PSTR("[OTA] No updates"));
  121. eepromRotate(true);
  122. break;
  123. case HTTP_UPDATE_OK:
  124. DEBUG_MSG_P(PSTR("[OTA] Done, restarting..."));
  125. deferredReset(500, CUSTOM_RESET_OTA); // wait a bit more than usual
  126. break;
  127. }
  128. }
  129. void _otaClientFromHttp(const String& url) {
  130. std::unique_ptr<WiFiClient> client(nullptr);
  131. if (ota::has_WiFiClient_argument<decltype(ESPhttpUpdate)>{}) {
  132. client = std::make_unique<WiFiClient>();
  133. }
  134. _otaClientRunUpdater(client.get(), url, "");
  135. }
  136. #if SECURE_CLIENT == SECURE_CLIENT_BEARSSL
  137. void _otaClientFromHttps(const String& url) {
  138. // Check for NTP early to avoid constructing SecureClient prematurely
  139. const int check = _ota_sc_config.on_check();
  140. if (!ntpSynced() && (check == SECURE_CLIENT_CHECK_CA)) {
  141. DEBUG_MSG_P(PSTR("[OTA] Time not synced! Cannot use CA validation\n"));
  142. return;
  143. }
  144. // unique_ptr self-destructs after exiting function scope
  145. // create the client on heap to use less stack space
  146. auto client = std::make_unique<SecureClient>(_ota_sc_config);
  147. if (!client->beforeConnected()) {
  148. return;
  149. }
  150. _otaClientRunUpdater(&client->get(), url);
  151. }
  152. #endif // SECURE_CLIENT_BEARSSL
  153. #if SECURE_CLIENT == SECURE_CLIENT_AXTLS
  154. void _otaClientFromHttps(const String& url) {
  155. // Note: this being the legacy option, only supporting legacy methods on ESPHttpUpdate itself
  156. // no way to know when it is connected, so no afterConnected
  157. const int check = _ota_sc_config.on_check();
  158. const String fp_string = _ota_sc_config.on_fingerprint();
  159. if (check == SECURE_CLIENT_CHECK_FINGERPRINT) {
  160. if (!fp_string.length() || !sslCheckFingerPrint(fp_string.c_str())) {
  161. DEBUG_MSG_P(PSTR("[OTA] Wrong fingerprint\n"));
  162. return;
  163. }
  164. }
  165. _otaClientRunUpdater(nullptr, url, fp_string);
  166. }
  167. #endif // SECURE_CLIENT_AXTLS
  168. void _otaClientFrom(const String& url) {
  169. if (url.startsWith("http://")) {
  170. _otaClientFromHttp(url);
  171. return;
  172. }
  173. #if SECURE_CLIENT != SECURE_CLIENT_NONE
  174. if (url.startsWith("https://")) {
  175. _otaClientFromHttps(url);
  176. return;
  177. }
  178. #endif
  179. DEBUG_MSG_P(PSTR("[OTA] Incorrect URL specified\n"));
  180. }
  181. #if TERMINAL_SUPPORT
  182. void _otaClientInitCommands() {
  183. terminalRegisterCommand(F("OTA"), [](Embedis* e) {
  184. if (e->argc < 2) {
  185. terminalError(F("OTA <url>"));
  186. } else {
  187. _otaClientFrom(String(e->argv[1]));
  188. terminalOK();
  189. }
  190. });
  191. }
  192. #endif // TERMINAL_SUPPORT
  193. #if (MQTT_SUPPORT && OTA_MQTT_SUPPORT)
  194. bool _ota_do_update = false;
  195. void _otaClientMqttCallback(unsigned int type, const char * topic, const char * payload) {
  196. if (type == MQTT_CONNECT_EVENT) {
  197. mqttSubscribe(MQTT_TOPIC_OTA);
  198. }
  199. if (type == MQTT_MESSAGE_EVENT) {
  200. const String t = mqttMagnitude((char *) topic);
  201. if (!_ota_do_update && t.equals(MQTT_TOPIC_OTA)) {
  202. DEBUG_MSG_P(PSTR("[OTA] Queuing from URL: %s\n"), payload);
  203. // TODO: c++14 support is required for `[_payload = String(payload)]() { ... }`
  204. // c++11 also supports basic `std::bind(func, arg)`, but we need to reset the lock
  205. _ota_do_update = true;
  206. const String _payload(payload);
  207. schedule_function([_payload]() {
  208. _otaClientFrom(_payload);
  209. _ota_do_update = false;
  210. });
  211. }
  212. }
  213. }
  214. #endif // MQTT_SUPPORT
  215. // -----------------------------------------------------------------------------
  216. void otaClientSetup() {
  217. // Backwards compatibility
  218. moveSetting("otafp", "otaFP");
  219. #if TERMINAL_SUPPORT
  220. _otaClientInitCommands();
  221. #endif
  222. #if (MQTT_SUPPORT && OTA_MQTT_SUPPORT)
  223. mqttRegister(_otaClientMqttCallback);
  224. #endif
  225. }
  226. #endif // OTA_CLIENT == OTA_CLIENT_HTTPUPDATE