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.

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