Mirror of espurna firmware for wireless switches and more
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.

222 lines
5.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. #include "espurna.h"
  9. #if OTA_CLIENT == OTA_CLIENT_HTTPUPDATE
  10. #include "mqtt.h"
  11. #include "ota.h"
  12. #include "system.h"
  13. #include "terminal.h"
  14. #include <ESP8266HTTPClient.h>
  15. #include <ESP8266httpUpdate.h>
  16. #include "libs/TypeChecks.h"
  17. #include "libs/SecureClientHelpers.h"
  18. #if SECURE_CLIENT != SECURE_CLIENT_NONE
  19. #include <WiFiClientSecure.h>
  20. namespace {
  21. #if OTA_SECURE_CLIENT_INCLUDE_CA
  22. #include "static/ota_client_trusted_root_ca.h"
  23. #else
  24. #include "static/digicert_evroot_pem.h"
  25. #define _ota_client_trusted_root_ca _ssl_digicert_ev_root_ca
  26. #endif
  27. } // namespace
  28. #endif // SECURE_CLIENT != SECURE_CLIENT_NONE
  29. // -----------------------------------------------------------------------------
  30. namespace espurna {
  31. namespace ota {
  32. namespace httpupdate {
  33. namespace {
  34. // -----------------------------------------------------------------------------
  35. // Generic update methods
  36. // -----------------------------------------------------------------------------
  37. void run(WiFiClient* client, const String& url) {
  38. // Disabling EEPROM rotation to prevent writing to EEPROM after the upgrade
  39. // Must happen right now, since HTTP updater will block until it's done
  40. eepromRotate(false);
  41. DEBUG_MSG_P(PSTR("[OTA] Downloading %s ...\n"), url.c_str());
  42. ESPhttpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
  43. ESPhttpUpdate.rebootOnUpdate(false);
  44. t_httpUpdate_return result = HTTP_UPDATE_NO_UPDATES;
  45. result = ESPhttpUpdate.update(*client, url);
  46. switch (result) {
  47. case HTTP_UPDATE_FAILED:
  48. DEBUG_MSG_P(PSTR("[OTA] Update failed (error %d): %s\n"), ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str());
  49. break;
  50. case HTTP_UPDATE_NO_UPDATES:
  51. DEBUG_MSG_P(PSTR("[OTA] No updates"));
  52. break;
  53. case HTTP_UPDATE_OK:
  54. DEBUG_MSG_P(PSTR("[OTA] Done, restarting..."));
  55. prepareReset(CustomResetReason::Ota);
  56. return;
  57. }
  58. eepromRotate(true);
  59. }
  60. void clientFromHttp(const String& url) {
  61. auto client = std::make_unique<WiFiClient>();
  62. run(client.get(), url);
  63. }
  64. #if SECURE_CLIENT == SECURE_CLIENT_BEARSSL
  65. void clientFromHttps(const String& url, SecureClientConfig& config) {
  66. // Check for NTP early to avoid constructing SecureClient prematurely
  67. const int check = config.on_check();
  68. if (!ntpSynced() && (check == SECURE_CLIENT_CHECK_CA)) {
  69. DEBUG_MSG_P(PSTR("[OTA] Time not synced! Cannot use CA validation\n"));
  70. return;
  71. }
  72. auto client = std::make_unique<SecureClient>(config);
  73. if (!client->beforeConnected()) {
  74. return;
  75. }
  76. run(&client->get(), url);
  77. }
  78. static SecureClientConfig defaultSecureClientConfig {
  79. .tag = "OTA",
  80. .on_check = []() -> int {
  81. return getSetting("otaScCheck", OTA_SECURE_CLIENT_CHECK);
  82. },
  83. .on_certificate = []() -> PGM_P {
  84. return _ota_client_trusted_root_ca;
  85. },
  86. .on_fingerprint = []() -> String {
  87. return getSetting("otaFP", OTA_FINGERPRINT);
  88. },
  89. .on_mfln = []() -> uint16_t {
  90. return getSetting("otaScMFLN", OTA_SECURE_CLIENT_MFLN);
  91. },
  92. .debug = true,
  93. };
  94. void clientFromHttps(const String& url) {
  95. clientFromHttps(url, defaultSecureClientConfig);
  96. }
  97. #endif // SECURE_CLIENT_BEARSSL
  98. namespace internal {
  99. String url;
  100. } // namespace internal
  101. void clientFromUrl(const String& url) {
  102. if (url.startsWith("http://")) {
  103. clientFromHttp(url);
  104. return;
  105. }
  106. #if SECURE_CLIENT != SECURE_CLIENT_NONE
  107. else if (url.startsWith("https://")) {
  108. clientFromHttps(url);
  109. return;
  110. }
  111. #endif
  112. DEBUG_MSG_P(PSTR("[OTA] Unsupported protocol\n"));
  113. }
  114. void clientFromInternalUrl() {
  115. const auto url = std::move(internal::url);
  116. clientFromUrl(url);
  117. }
  118. [[gnu::unused]]
  119. void clientQueueUrl(espurna::StringView url) {
  120. internal::url = url.toString();
  121. espurnaRegisterOnceUnique(clientFromInternalUrl);
  122. }
  123. #if TERMINAL_SUPPORT
  124. PROGMEM_STRING(OtaCommand, "OTA");
  125. static void otaCommand(::terminal::CommandContext&& ctx) {
  126. if (ctx.argv.size() != 2) {
  127. terminalError(ctx, F("OTA <URL>"));
  128. return;
  129. }
  130. clientFromUrl(ctx.argv[1]);
  131. terminalOK(ctx);
  132. }
  133. static constexpr ::terminal::Command OtaCommands[] PROGMEM {
  134. {OtaCommand, otaCommand},
  135. };
  136. void terminalSetup() {
  137. espurna::terminal::add(OtaCommands);
  138. }
  139. #endif // TERMINAL_SUPPORT
  140. #if (MQTT_SUPPORT && OTA_MQTT_SUPPORT)
  141. void mqttCallback(unsigned int type, StringView topic, StringView payload) {
  142. if (type == MQTT_CONNECT_EVENT) {
  143. mqttSubscribe(MQTT_TOPIC_OTA);
  144. return;
  145. }
  146. if (type == MQTT_MESSAGE_EVENT) {
  147. const auto t = mqttMagnitude(topic);
  148. if (!internal::url.length() && t.equals(MQTT_TOPIC_OTA)) {
  149. clientQueueUrl(payload);
  150. }
  151. return;
  152. }
  153. }
  154. #endif // MQTT_SUPPORT
  155. } // namespace
  156. } // namespace httpupdate
  157. } // namespace ota
  158. } // namespace espurna
  159. // -----------------------------------------------------------------------------
  160. void otaClientSetup() {
  161. moveSetting("otafp", "otaFP");
  162. #if TERMINAL_SUPPORT
  163. espurna::ota::httpupdate::terminalSetup();
  164. #endif
  165. #if (MQTT_SUPPORT && OTA_MQTT_SUPPORT)
  166. mqttRegister(espurna::ota::httpupdate::mqttCallback);
  167. #endif
  168. }
  169. #endif // OTA_CLIENT == OTA_CLIENT_HTTPUPDATE