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.

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