ESP8266-based WiFi serial modem emulator ROM
at main 297 lines 7.7 kB view raw
1/* 2 * WiFiPPP 3 * Copyright (c) 2021 joshua stein <jcs@jcs.org> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18#include <WiFiClient.h> 19#include <WiFiClientSecure.h> 20#include "wifippp.h" 21 22static const char OTA_VERSION_URL[] PROGMEM = 23 "https://raw.githubusercontent.com/jcs/WiFiPPP/main/release/version.txt"; 24 25static WiFiClient client; 26static WiFiClientSecure client_tls; 27static bool tls = false; 28 29bool 30update_https_get_body(const char *url, long expected_length) 31{ 32 char *host, *path, *colon; 33 int status = 0, port, httpver, chars, lines, tlength, clength = -1; 34 35 if (WiFi.status() != WL_CONNECTED) { 36 output("ERROR WiFi is not connected\r\n"); 37 return false; 38 } 39 40 host = (char *)malloc(strlen(url) + 1); 41 if (host == NULL) { 42 output("ERROR malloc failed\r\n"); 43 return false; 44 } 45 46 path = (char *)malloc(strlen(url) + 1); 47 if (path == NULL) { 48 output("ERROR malloc failed\r\n"); 49 free(host); 50 return false; 51 } 52 53 if (sscanf(url, "http://%[^/]%s%n", host, path, &chars) == 2 && 54 chars != 0) { 55 tls = false; 56 } else if (sscanf(url, "https://%[^/]%s%n", host, path, &chars) == 2 57 && chars != 0) { 58 tls = true; 59 /* 60 * This would be nice to not have to do, but keeping up with 61 * GitHub's TLS cert fingerprints will be tedious, and we have 62 * no cert chain. 63 */ 64 client_tls.setInsecure(); 65 } else { 66 outputf("ERROR failed parsing URL \"%s\"\r\n", url); 67 free(path); 68 free(host); 69 return false; 70 } 71 72 if ((colon = strchr(host, ':'))) { 73 colon[0] = '\0'; 74 port = atoi(colon + 1); 75 } else { 76 port = (tls ? 443 : 80); 77 } 78 79#ifdef UPDATE_TRACE 80 syslog.logf(LOG_DEBUG, "%s: host \"%s\" path \"%s\" port %d tls %d", 81 __func__, host, path, port, tls ? 1 : 0); 82#endif 83 84 if (!(tls ? client_tls : client).connect(host, port)) { 85 outputf("ERROR OTA failed connecting to http%s://%s:%d\r\n", 86 tls ? "s" : "", host, port); 87 free(path); 88 free(host); 89 return false; 90 } 91 92 (tls ? client_tls : client).printf("GET %s HTTP/1.0\r\n", path); 93 (tls ? client_tls : client).printf("Host: %s\r\n", host); 94 (tls ? client_tls : client).printf("User-Agent: WiFiPPP %s\r\n", WIFIPPP_VERSION); 95 (tls ? client_tls : client).printf("Connection: close\r\n"); 96 (tls ? client_tls : client).printf("\r\n"); 97 98 free(path); 99 free(host); 100 101 /* read headers */ 102 lines = 0; 103 while ((tls ? client_tls : client).connected() || 104 (tls ? client_tls : client).available()) { 105 String line = (tls ? client_tls : client).readStringUntil('\n'); 106 107#ifdef UPDATE_TRACE 108 syslog.logf(LOG_DEBUG, "%s: read header \"%s\"", __func__, 109 line.c_str()); 110#endif 111 112 if (lines == 0) 113 sscanf(line.c_str(), "HTTP/1.%d %d%n", &httpver, 114 &status, &chars); 115 else if (sscanf(line.c_str(), "Content-Length: %d%n", 116 &tlength, &chars) == 1 && chars > 0) 117 clength = tlength; 118 else if (line == "\r") 119 break; 120 121 lines++; 122 } 123 124#ifdef UPDATE_TRACE 125 syslog.logf(LOG_DEBUG, "%s: read status %d, content-length %d vs " 126 "expected %ld, finished on line %d", __func__, status, clength, 127 expected_length, lines); 128#endif 129 130 if (status != 200) { 131 outputf("ERROR OTA fetch of %s failed with HTTP status %d\r\n", 132 url, status); 133 goto drain; 134 } 135 136 if (expected_length != 0 && clength != expected_length) { 137 outputf("ERROR OTA fetch of %s expected to be size %d, " 138 "fetched %d\r\n", url, expected_length, clength); 139 goto drain; 140 } 141 142 return true; 143 144drain: 145#ifdef UPDATE_TRACE 146 syslog.logf(LOG_DEBUG, "%s: draining remaining body", __func__); 147#endif 148 while ((tls ? client_tls : client).available()) 149 (tls ? client_tls : client).read(); 150 (tls ? client_tls : client).stop(); 151 return false; 152} 153 154void 155update_process(char *url, bool do_update, bool force) 156{ 157 String rom_url, md5, version; 158 int bytesize = 0, lines = 0, len; 159 char *furl = NULL; 160 161 outputf("\n"); 162 163 if (url == NULL) { 164 furl = url = (char *)malloc(len = (strlen_P(OTA_VERSION_URL) + 165 1)); 166 if (url == NULL) { 167 output("ERROR malloc failed\r\n"); 168 return; 169 } 170 memcpy_P(url, OTA_VERSION_URL, len); 171 } 172 173#ifdef UPDATE_TRACE 174 syslog.logf(LOG_DEBUG, "fetching update manifest from \"%s\"", url); 175#endif 176 177 if (!update_https_get_body(url, 0)) { 178#ifdef UPDATE_TRACE 179 syslog.logf(LOG_DEBUG, "update_https_get_body(%s) failed", url); 180#endif 181 if (furl) 182 free(furl); 183 return; 184 } 185 if (furl) 186 free(furl); 187 188#ifdef UPDATE_TRACE 189 syslog.logf(LOG_DEBUG, "reading body of manifest (available %d)", 190 (tls ? client_tls : client).available()); 191#endif 192 193 lines = 0; 194 while ((tls ? client_tls : client).connected() || 195 (tls ? client_tls : client).available()) { 196 String line = (tls ? client_tls : client).readStringUntil('\n'); 197 198#ifdef UPDATE_TRACE 199 syslog.logf(LOG_DEBUG, "%s: read body[%d] \"%s\"", __func__, 200 lines, line.c_str()); 201#endif 202 203 switch (lines) { 204 case 0: 205 version = line; 206 break; 207 case 1: 208 bytesize = atoi(line.c_str()); 209 break; 210 case 2: 211 md5 = line; 212 break; 213 case 3: 214 rom_url = line; 215 break; 216 default: 217#ifdef UPDATE_TRACE 218 syslog.logf("%s: unexpected line %d: %s\r\n", __func__, 219 lines + 1, line.c_str()); 220#endif 221 break; 222 } 223 224 lines++; 225 } 226 227 (tls ? client_tls : client).stop(); 228 229#ifdef UPDATE_TRACE 230 syslog.logf(LOG_DEBUG, "done reading body after %d lines", lines); 231#endif 232 233 if (version == WIFIPPP_VERSION && !force) { 234 outputf("ERROR OTA server reports version %s, no update " 235 "available\r\n", version.c_str()); 236 return; 237 } else if (!do_update) { 238 outputf("OK version %s (%d bytes) available, use AT$UPDATE " 239 "to update\r\n", version.c_str(), bytesize); 240 return; 241 } 242 243 /* doing an update, parse the url read */ 244#ifdef UPDATE_TRACE 245 syslog.logf(LOG_DEBUG, "%s: doing update with ROM url \"%s\" size %d", 246 __func__, rom_url.c_str(), bytesize); 247#endif 248 if (!update_https_get_body((char *)rom_url.c_str(), bytesize)) 249 return; 250 251 state = STATE_UPDATING; 252 pixel_color_by_state(); 253 254 outputf("Updating to version %s (%d bytes) from %s\r\n", 255 version.c_str(), bytesize, (char *)rom_url.c_str()); 256 257 Update.begin(bytesize, U_FLASH, -1); 258 259 Update.setMD5(md5.c_str()); 260 261 Update.onProgress([](unsigned int progress, unsigned int total) { 262 /* 263 * Just force serial output here rather than using outputf, 264 * don't let it block us. 265 */ 266 if (progress == 1 || progress % 1024 == 0 || progress == total) 267 Serial.printf("\rFlash update progress:% 7d of %d", 268 progress, total); 269 }); 270 271 if ((int)Update.writeStream((tls ? client_tls : client)) != bytesize) { 272 if (Update.getError() == UPDATE_ERROR_BOOTSTRAP) 273 outputf("ERROR update must be done from fresh " 274 "reset, not from uploaded code\r\n"); 275 else 276 outputf("ERROR failed writing download bytes: %d\r\n", 277 Update.getError()); 278 279 while ((tls ? client_tls : client).available()) 280 (tls ? client_tls : client).read(); 281 282 (tls ? client_tls : client).stop(); 283 return; 284 } 285 286 if (!Update.end()) { 287 outputf("ERROR failed update at finish: %d\r\n", 288 Update.getError()); 289 return; 290 } 291 292 (tls ? client_tls : client).stop(); 293 outputf("\r\nOK update completed, restarting\r\n"); 294 295 delay(500); 296 ESP.restart(); 297}