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.

297 lines
8.4 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 "libs/URL.h"
  13. #include "libs/TypeChecks.h"
  14. #if SECURE_CLIENT != SECURE_CLIENT_NONE
  15. #if OTA_SECURE_CLIENT_INCLUDE_CA
  16. #include "static/ota_client_trusted_root_ca.h"
  17. #else
  18. #include "static/digicert_evroot_pem.h"
  19. #define _ota_client_trusted_root_ca _ssl_digicert_ev_root_ca
  20. #endif
  21. #endif // SECURE_CLIENT != SECURE_CLIENT_NONE
  22. template <typename T>
  23. void _otaFollowRedirects(const std::true_type&, T& instance) {
  24. instance.followRedirects(true);
  25. }
  26. template <typename T>
  27. void _otaFollowRedirects(const std::false_type&, T& instance) {
  28. }
  29. template <typename T>
  30. t_httpUpdate_return _otaClientUpdate(const std::true_type&, T& instance, WiFiClient* client, const String& url) {
  31. return instance.update(*client, url);
  32. }
  33. template <typename T>
  34. t_httpUpdate_return _otaClientUpdate(const std::false_type&, T& instance, WiFiClient*, const String& url) {
  35. return instance.update(url);
  36. }
  37. namespace ota {
  38. template <typename T>
  39. using has_followRedirects_t = decltype(std::declval<T>().followRedirects(std::declval<bool>()));
  40. template <typename T>
  41. using has_followRedirects = is_detected<has_followRedirects_t, T>;
  42. template <typename T>
  43. using has_WiFiClient_argument_t = decltype(std::declval<T>().update(std::declval<WiFiClient&>(), std::declval<const String&>()));
  44. template <typename T>
  45. using has_WiFiClient_argument = is_detected<has_WiFiClient_argument_t, T>;
  46. }
  47. void _otaClientRunUpdater(WiFiClient* client, const String& url, const String& fp = "") {
  48. UNUSED(client);
  49. UNUSED(fp);
  50. // Disabling EEPROM rotation to prevent writing to EEPROM after the upgrade
  51. eepromRotate(false);
  52. DEBUG_MSG_P(PSTR("[OTA] Downloading %s ...\n"), url.c_str());
  53. // TODO: support currentVersion (string arg after 'url')
  54. // NOTE: ESPhttpUpdate.update(..., fp) will **always** fail with empty fingerprint
  55. // NOTE: It is possible to support BearSSL with 2.4.2 by using uint8_t[20] instead of String for fingerprint argument
  56. _otaFollowRedirects(ota::has_followRedirects<decltype(ESPhttpUpdate)>{}, ESPhttpUpdate);
  57. ESPhttpUpdate.rebootOnUpdate(false);
  58. t_httpUpdate_return result = HTTP_UPDATE_NO_UPDATES;
  59. // We expect both .update(url, "", String_fp) and .update(url) to survice until axTLS is removed from the Core
  60. #if (SECURE_CLIENT == SECURE_CLIENT_AXTLS)
  61. if (url.startsWith("https://")) {
  62. result = ESPhttpUpdate.update(url, "", fp);
  63. } else {
  64. result = ESPhttpUpdate.update(url);
  65. }
  66. #else
  67. result = _otaClientUpdate(ota::has_WiFiClient_argument<decltype(ESPhttpUpdate)>{}, ESPhttpUpdate, client, url);
  68. #endif
  69. switch (result) {
  70. case HTTP_UPDATE_FAILED:
  71. DEBUG_MSG_P(PSTR("[OTA] Update failed (error %d): %s\n"), ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str());
  72. eepromRotate(true);
  73. break;
  74. case HTTP_UPDATE_NO_UPDATES:
  75. DEBUG_MSG_P(PSTR("[OTA] No updates"));
  76. eepromRotate(true);
  77. break;
  78. case HTTP_UPDATE_OK:
  79. DEBUG_MSG_P(PSTR("[OTA] Done, restarting..."));
  80. deferredReset(500, CUSTOM_RESET_OTA); // wait a bit more than usual
  81. break;
  82. }
  83. }
  84. void _otaClientFromHttp(const String& url) {
  85. std::unique_ptr<WiFiClient> client(nullptr);
  86. if (ota::has_WiFiClient_argument<decltype(ESPhttpUpdate)>{}) {
  87. client = std::make_unique<WiFiClient>();
  88. }
  89. _otaClientRunUpdater(client.get(), url, "");
  90. }
  91. #if SECURE_CLIENT == SECURE_CLIENT_BEARSSL
  92. void _otaClientFromHttps(const String& url) {
  93. int check = getSetting("otaScCheck", OTA_SECURE_CLIENT_CHECK).toInt();
  94. bool settime = (check == SECURE_CLIENT_CHECK_CA);
  95. if (!ntpSynced() && settime) {
  96. DEBUG_MSG_P(PSTR("[OTA] Time not synced!\n"));
  97. return;
  98. }
  99. // unique_ptr self-destructs after exiting function scope
  100. // create WiFiClient on heap to use less stack space
  101. auto client = std::make_unique<BearSSL::WiFiClientSecure>();
  102. if (check == SECURE_CLIENT_CHECK_NONE) {
  103. DEBUG_MSG_P(PSTR("[OTA] !!! Connection will not be validated !!!\n"));
  104. client->setInsecure();
  105. }
  106. if (check == SECURE_CLIENT_CHECK_FINGERPRINT) {
  107. String fp_string = getSetting("otaFP", OTA_FINGERPRINT);
  108. if (!fp_string.length()) {
  109. DEBUG_MSG_P(PSTR("[OTA] Requested fingerprint auth, but 'otaFP' is not set\n"));
  110. return;
  111. }
  112. uint8_t fp_bytes[20] = {0};
  113. sslFingerPrintArray(fp_string.c_str(), fp_bytes);
  114. client->setFingerprint(fp_bytes);
  115. }
  116. BearSSL::X509List *ca = nullptr;
  117. if (check == SECURE_CLIENT_CHECK_CA) {
  118. ca = new BearSSL::X509List(_ota_client_trusted_root_ca);
  119. // because we do not support libc methods of getting time, force client to use ntpclientlib's current time
  120. // XXX: local2utc method use is detrimental when DST happening. now() should be utc
  121. client->setX509Time(ntpLocal2UTC(now()));
  122. client->setTrustAnchors(ca);
  123. }
  124. // TODO: RX and TX buffer sizes must be equal?
  125. const uint16_t requested_mfln = getSetting("otaScMFLN", OTA_SECURE_CLIENT_MFLN).toInt();
  126. switch (requested_mfln) {
  127. // default, do nothing
  128. case 0:
  129. break;
  130. // match valid sizes only
  131. case 512:
  132. case 1024:
  133. case 2048:
  134. case 4096:
  135. {
  136. client->setBufferSizes(requested_mfln, requested_mfln);
  137. break;
  138. }
  139. default:
  140. DEBUG_MSG_P(PSTR("[OTA] Warning: MFLN buffer size must be one of 512, 1024, 2048 or 4096\n"));
  141. }
  142. _otaClientRunUpdater(client.get(), url);
  143. }
  144. #endif // SECURE_CLIENT_BEARSSL
  145. #if SECURE_CLIENT == SECURE_CLIENT_AXTLS
  146. void _otaClientFromHttps(const String& url) {
  147. const int check = getSetting("otaScCheck", OTA_SECURE_CLIENT_CHECK).toInt();
  148. String fp_string;
  149. if (check == SECURE_CLIENT_CHECK_FINGERPRINT) {
  150. fp_string = getSetting("otaFP", OTA_FINGERPRINT);
  151. if (!fp_string.length() || !sslCheckFingerPrint(fp_string.c_str())) {
  152. DEBUG_MSG_P(PSTR("[OTA] Wrong fingerprint\n"));
  153. return;
  154. }
  155. }
  156. _otaClientRunUpdater(nullptr, url, fp_string);
  157. }
  158. #endif // SECURE_CLIENT_AXTLS
  159. void _otaClientFrom(const String& url) {
  160. if (url.startsWith("http://")) {
  161. _otaClientFromHttp(url);
  162. return;
  163. }
  164. #if SECURE_CLIENT != SECURE_CLIENT_NONE
  165. if (url.startsWith("https://")) {
  166. _otaClientFromHttps(url);
  167. return;
  168. }
  169. #endif
  170. DEBUG_MSG_P(PSTR("[OTA] Incorrect URL specified\n"));
  171. }
  172. #if TERMINAL_SUPPORT
  173. void _otaClientInitCommands() {
  174. terminalRegisterCommand(F("OTA"), [](Embedis* e) {
  175. if (e->argc < 2) {
  176. terminalError(F("OTA <url>"));
  177. } else {
  178. _otaClientFrom(String(e->argv[1]));
  179. terminalOK();
  180. }
  181. });
  182. }
  183. #endif // TERMINAL_SUPPORT
  184. #if (MQTT_SUPPORT && OTA_MQTT_SUPPORT)
  185. bool _ota_do_update = false;
  186. String _ota_url;
  187. void _otaClientLoop() {
  188. if (_ota_do_update) {
  189. _otaClientFrom(_ota_url);
  190. _ota_do_update = false;
  191. _ota_url = "";
  192. }
  193. }
  194. void _otaClientMqttCallback(unsigned int type, const char * topic, const char * payload) {
  195. if (type == MQTT_CONNECT_EVENT) {
  196. mqttSubscribe(MQTT_TOPIC_OTA);
  197. }
  198. if (type == MQTT_MESSAGE_EVENT) {
  199. String t = mqttMagnitude((char *) topic);
  200. if (t.equals(MQTT_TOPIC_OTA)) {
  201. DEBUG_MSG_P(PSTR("[OTA] Queuing from URL: %s\n"), payload);
  202. _ota_do_update = true;
  203. _ota_url = payload;
  204. }
  205. }
  206. }
  207. #endif // MQTT_SUPPORT
  208. // -----------------------------------------------------------------------------
  209. void otaClientSetup() {
  210. // Backwards compatibility
  211. moveSetting("otafp", "otaFP");
  212. #if TERMINAL_SUPPORT
  213. _otaClientInitCommands();
  214. #endif
  215. #if (MQTT_SUPPORT && OTA_MQTT_SUPPORT)
  216. mqttRegister(_otaClientMqttCallback);
  217. espurnaRegisterLoop(_otaClientLoop);
  218. #endif
  219. }
  220. #endif // OTA_CLIENT == OTA_CLIENT_HTTPUPDATE