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.

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