Bringing WiFi to the Cidco Mailstation
at main 295 lines 7.6 kB view raw
1/* 2 * WiFiStation 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 "wifistation.h" 21 22static const char OTA_VERSION_URL[] PROGMEM = 23 "https://raw.githubusercontent.com/jcs/WiFiStation/main/release/version.txt"; 24 25static WiFiClient client; 26static WiFiClientSecure client_tls; 27static bool tls = false; 28 29bool 30update_http_read_until_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 if (url == NULL) { 41 outputf("ERROR failed parsing NULL URL\r\n"); 42 return false; 43 } 44 45 host = (char *)malloc(strlen(url) + 1); 46 if (host == NULL) { 47 output("ERROR malloc failed\r\n"); 48 return false; 49 } 50 51 path = (char *)malloc(strlen(url) + 1); 52 if (path == NULL) { 53 output("ERROR malloc failed\r\n"); 54 free(host); 55 return false; 56 } 57 58 if (sscanf(url, "http://%[^/]%s%n", host, path, &chars) == 2 && 59 chars != 0) { 60 tls = false; 61 } else if (sscanf(url, "https://%[^/]%s%n", host, path, &chars) == 2 62 && chars != 0) { 63 tls = true; 64 /* 65 * This would be nice to not have to do, but keeping up with 66 * GitHub's TLS cert fingerprints will be tedious, and we have 67 * no cert chain. 68 */ 69 client_tls.setInsecure(); 70 } else { 71 outputf("ERROR failed parsing URL \"%s\"\r\n", url); 72 free(path); 73 free(host); 74 return false; 75 } 76 77 if ((colon = strchr(host, ':'))) { 78 colon[0] = '\0'; 79 port = atoi(colon + 1); 80 } else { 81 port = (tls ? 443 : 80); 82 } 83 84#ifdef UPDATE_TRACE 85 syslog.logf(LOG_DEBUG, "%s: host \"%s\" path \"%s\" port %d tls %d", 86 __func__, host, path, port, tls ? 1 : 0); 87#endif 88 89 if (!(tls ? client_tls : client).connect(host, port)) { 90 outputf("ERROR OTA failed connecting to http%s://%s:%d\r\n", 91 tls ? "s" : "", host, port); 92 free(path); 93 free(host); 94 return false; 95 } 96 97 (tls ? client_tls : client).printf("GET %s HTTP/1.0\r\n", path); 98 (tls ? client_tls : client).printf("Host: %s\r\n", host); 99 (tls ? client_tls : client).printf("User-Agent: WiFiStation %s\r\n", 100 WIFISTATION_VERSION); 101 (tls ? client_tls : client).printf("Connection: close\r\n"); 102 (tls ? client_tls : client).printf("\r\n"); 103 104 free(path); 105 free(host); 106 107 /* read headers */ 108 lines = 0; 109 while ((tls ? client_tls : client).connected() || 110 (tls ? client_tls : client).available()) { 111 String line = (tls ? client_tls : client).readStringUntil('\n'); 112 113#ifdef UPDATE_TRACE 114 syslog.logf(LOG_DEBUG, "%s: read header \"%s\"", __func__, 115 line.c_str()); 116#endif 117 118 if (lines == 0) 119 sscanf(line.c_str(), "HTTP/1.%d %d%n", &httpver, 120 &status, &chars); 121 else if (sscanf(line.c_str(), "Content-Length: %d%n", 122 &tlength, &chars) == 1 && chars > 0) 123 clength = tlength; 124 else if (line == "\r") 125 break; 126 127 lines++; 128 } 129 130#ifdef UPDATE_TRACE 131 syslog.logf(LOG_DEBUG, "%s: read status %d, content-length %d vs " 132 "expected %ld", __func__, status, clength, expected_length); 133#endif 134 135 if (status != 200) { 136 outputf("ERROR OTA fetch of %s failed with HTTP status %d\r\n", 137 url, status); 138 goto drain; 139 } 140 141 if (expected_length != 0 && clength != expected_length) { 142 outputf("ERROR OTA fetch of %s expected to be size %d, " 143 "fetched %d\r\n", url, expected_length, clength); 144 goto drain; 145 } 146 147 while ((tls ? client_tls : client).connected() && 148 !(tls ? client_tls : client).available()) 149 ESP.wdtFeed(); 150 151 return true; 152 153drain: 154#ifdef UPDATE_TRACE 155 syslog.logf(LOG_DEBUG, "%s: draining remaining body", __func__); 156#endif 157 while ((tls ? client_tls : client).available()) 158 (tls ? client_tls : client).read(); 159 (tls ? client_tls : client).stop(); 160 return false; 161} 162 163void 164update_process(char *url, bool do_update, bool force) 165{ 166 String rom_url, md5, version; 167 int bytesize = 0, lines = 0, len; 168 char *furl = NULL; 169 170 output("\n"); 171 172 if (url == NULL) { 173 furl = url = (char *)malloc(len = (strlen_P(OTA_VERSION_URL) + 174 1)); 175 if (url == NULL) { 176 output("ERROR malloc failed\r\n"); 177 return; 178 } 179 memcpy_P(url, OTA_VERSION_URL, len); 180 } 181 182#ifdef UPDATE_TRACE 183 syslog.logf(LOG_DEBUG, "fetching update manifest from \"%s\"", url); 184#endif 185 186 if (!update_http_read_until_body(url, 0)) { 187#ifdef UPDATE_TRACE 188 syslog.logf(LOG_DEBUG, "read until body(%s) failed", url); 189#endif 190 if (furl) 191 free(furl); 192 return; 193 } 194 if (furl) 195 free(furl); 196 197 lines = 0; 198 while ((tls ? client_tls : client).connected() || 199 (tls ? client_tls : client).available()) { 200 String line = (tls ? client_tls : client).readStringUntil('\n'); 201 202#ifdef UPDATE_TRACE 203 syslog.logf(LOG_DEBUG, "%s: read body[%d] \"%s\"", __func__, 204 lines, line.c_str()); 205#endif 206 207 switch (lines) { 208 case 0: 209 version = line; 210 break; 211 case 1: 212 bytesize = atoi(line.c_str()); 213 break; 214 case 2: 215 md5 = line; 216 break; 217 case 3: 218 rom_url = line; 219 break; 220 default: 221#ifdef UPDATE_TRACE 222 syslog.logf("%s: unexpected line %d: %s\r\n", __func__, 223 lines + 1, line.c_str()); 224#endif 225 break; 226 } 227 228 lines++; 229 } 230 231 (tls ? client_tls : client).stop(); 232 233 if (version == WIFISTATION_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_http_read_until_body((char *)rom_url.c_str(), bytesize)) 249 return; 250 251 outputf("Updating to version %s (%d bytes) from %s\r\n", 252 version.c_str(), bytesize, (char *)rom_url.c_str()); 253 254 Update.begin(bytesize, U_FLASH, pRedLED); 255 256 Update.setMD5(md5.c_str()); 257 258 Update.onProgress([](unsigned int progress, unsigned int total) { 259 outputf("\rFlash update progress: % 6u of % 6u", progress, 260 total); 261 }); 262 263 if ((int)Update.writeStream((tls ? client_tls : client)) != bytesize) { 264 switch (Update.getError()) { 265 case UPDATE_ERROR_BOOTSTRAP: 266 outputf("\nERROR update must be done from fresh " 267 "reset, not from uploaded code\r\n"); 268 break; 269 case UPDATE_ERROR_MAGIC_BYTE: 270 outputf("\nERROR image does not start with 0xE9\r\n"); 271 break; 272 default: 273 outputf("\nERROR failed writing download bytes: %d\r\n", 274 Update.getError()); 275 } 276 277 while ((tls ? client_tls : client).available()) 278 (tls ? client_tls : client).read(); 279 280 (tls ? client_tls : client).stop(); 281 return; 282 } 283 284 if (!Update.end()) { 285 outputf("\nERROR failed update at finish: %d\r\n", 286 Update.getError()); 287 return; 288 } 289 290 (tls ? client_tls : client).stop(); 291 outputf("\r\nOK update completed, restarting\r\n"); 292 293 delay(500); 294 ESP.restart(); 295}