|
18 | 18 | */
|
19 | 19 |
|
20 | 20 | #include "SFU.h"
|
| 21 | +#include <BlockDevice.h> |
| 22 | +#include <MBRBlockDevice.h> |
| 23 | +#include "FATFileSystem.h" |
| 24 | +#include <Arduino_DebugUtils.h> |
| 25 | +#include <WiFiC3.h> |
| 26 | +#include <WiFiSSLClient.h> |
| 27 | + |
| 28 | +#define AIOT_CONFIG_PORTENTA_C33_OTA_HTTP_HEADER_RECEIVE_TIMEOUT_ms (5*1000UL); |
| 29 | +#define AIOT_CONFIG_PORTENTA_C33_OTA_HTTP_DATA_RECEIVE_TIMEOUT_ms (5*60*1000UL); |
21 | 30 |
|
22 | 31 | const unsigned char SFU[0x20000] __attribute__ ((section(".second_stage_ota"), used)) = {
|
23 | 32 | #include "c33.h"
|
24 | 33 | };
|
| 34 | + |
| 35 | +BlockDevice* block_device = BlockDevice::get_default_instance(); |
| 36 | +MBRBlockDevice mbr(block_device, 1); |
| 37 | +FATFileSystem fs("ota"); |
| 38 | + |
| 39 | +/* Original code: http://stackoverflow.com/questions/2616011/easy-way-to-parse-a-url-in-c-cross-platform */ |
| 40 | +#include <string> |
| 41 | +#include <algorithm> |
| 42 | +#include <cctype> |
| 43 | +#include <functional> |
| 44 | +#include <iostream> |
| 45 | + |
| 46 | +struct URI { |
| 47 | + public: |
| 48 | + URI(const std::string& url_s) { |
| 49 | + this->parse(url_s); |
| 50 | + } |
| 51 | + std::string protocol_, host_, path_, query_; |
| 52 | + private: |
| 53 | + void parse(const std::string& url_s); |
| 54 | +}; |
| 55 | + |
| 56 | +using namespace std; |
| 57 | + |
| 58 | +// ctors, copy, equality, ... |
| 59 | +// TODO: change me into something embedded friendly (this function adds ~100KB to flash) |
| 60 | +void URI::parse(const string& url_s) |
| 61 | +{ |
| 62 | + const string prot_end("://"); |
| 63 | + string::const_iterator prot_i = search(url_s.begin(), url_s.end(), |
| 64 | + prot_end.begin(), prot_end.end()); |
| 65 | + protocol_.reserve(distance(url_s.begin(), prot_i)); |
| 66 | + transform(url_s.begin(), prot_i, |
| 67 | + back_inserter(protocol_), |
| 68 | + ptr_fun<int,int>(tolower)); // protocol is icase |
| 69 | + if( prot_i == url_s.end() ) |
| 70 | + return; |
| 71 | + advance(prot_i, prot_end.length()); |
| 72 | + string::const_iterator path_i = find(prot_i, url_s.end(), '/'); |
| 73 | + host_.reserve(distance(prot_i, path_i)); |
| 74 | + transform(prot_i, path_i, |
| 75 | + back_inserter(host_), |
| 76 | + ptr_fun<int,int>(tolower)); // host is icase |
| 77 | + string::const_iterator query_i = find(path_i, url_s.end(), '?'); |
| 78 | + path_.assign(path_i, query_i); |
| 79 | + if( query_i != url_s.end() ) |
| 80 | + ++query_i; |
| 81 | + query_.assign(query_i, url_s.end()); |
| 82 | +} |
| 83 | + |
| 84 | +int SFU::download(const char* ota_url) { |
| 85 | + int err = -1; |
| 86 | + |
| 87 | + if ((err = fs.reformat(&mbr)) != 0) |
| 88 | + { |
| 89 | + DEBUG_ERROR("%s: fs.reformat() failed with %d", __FUNCTION__, err); |
| 90 | + return static_cast<int>(OTAError::PORTENTA_C33_ErrorReformat); |
| 91 | + } |
| 92 | + |
| 93 | + FILE * file = fopen("/ota/UPDATE.BIN.OTA", "wb"); |
| 94 | + if (!file) |
| 95 | + { |
| 96 | + DEBUG_ERROR("%s: fopen() failed", __FUNCTION__); |
| 97 | + fclose(file); |
| 98 | + return static_cast<int>(OTAError::PORTENTA_C33_ErrorOpenUpdateFile); |
| 99 | + } |
| 100 | + |
| 101 | + URI url(ota_url); |
| 102 | + Client * client = nullptr; |
| 103 | + int port = 0; |
| 104 | + |
| 105 | + if (url.protocol_ == "http") { |
| 106 | + client = new WiFiClient(); |
| 107 | + port = 80; |
| 108 | + } else if (url.protocol_ == "https") { |
| 109 | + client = new WiFiSSLClient(); |
| 110 | + port = 443; |
| 111 | + } else { |
| 112 | + DEBUG_ERROR("%s: Failed to parse OTA URL %s", __FUNCTION__, url.host_.c_str()); |
| 113 | + fclose(file); |
| 114 | + return static_cast<int>(OTAError::PORTENTA_C33_UrlParseError); |
| 115 | + } |
| 116 | + |
| 117 | + if (!client->connect(url.host_.c_str(), port)) |
| 118 | + { |
| 119 | + DEBUG_ERROR("%s: Connection failure with OTA storage server %s", __FUNCTION__, url.host_.c_str()); |
| 120 | + fclose(file); |
| 121 | + return static_cast<int>(OTAError::PORTENTA_C33_ServerConnectError); |
| 122 | + } |
| 123 | + |
| 124 | + client->println(String("GET ") + url.path_.c_str() + " HTTP/1.1"); |
| 125 | + client->println(String("Host: ") + url.host_.c_str()); |
| 126 | + client->println("Connection: close"); |
| 127 | + client->println(); |
| 128 | + |
| 129 | + /* Receive HTTP header. */ |
| 130 | + String http_header; |
| 131 | + bool is_header_complete = false, |
| 132 | + is_http_header_timeout = false; |
| 133 | + for (unsigned long const start = millis(); !is_header_complete;) |
| 134 | + { |
| 135 | + is_http_header_timeout = (millis() - start) > AIOT_CONFIG_PORTENTA_C33_OTA_HTTP_HEADER_RECEIVE_TIMEOUT_ms; |
| 136 | + if (is_http_header_timeout) break; |
| 137 | + |
| 138 | + if (client->available()) |
| 139 | + { |
| 140 | + char const c = client->read(); |
| 141 | + |
| 142 | + http_header += c; |
| 143 | + if (http_header.endsWith("\r\n\r\n")) |
| 144 | + is_header_complete = true; |
| 145 | + } |
| 146 | + } |
| 147 | + |
| 148 | + if (!is_header_complete) |
| 149 | + { |
| 150 | + DEBUG_ERROR("%s: Error receiving HTTP header %s", __FUNCTION__, is_http_header_timeout ? "(timeout)":""); |
| 151 | + fclose(file); |
| 152 | + return static_cast<int>(OTAError::PORTENTA_C33_HttpHeaderError); |
| 153 | + } |
| 154 | + |
| 155 | + /* Extract concent length from HTTP header. A typical entry looks like |
| 156 | + * "Content-Length: 123456" |
| 157 | + */ |
| 158 | + char const * content_length_ptr = strstr(http_header.c_str(), "Content-Length"); |
| 159 | + if (!content_length_ptr) |
| 160 | + { |
| 161 | + DEBUG_ERROR("%s: Failure to extract content length from http header", __FUNCTION__); |
| 162 | + fclose(file); |
| 163 | + return static_cast<int>(OTAError::PORTENTA_C33_ErrorParseHttpHeader); |
| 164 | + } |
| 165 | + /* Find start of numerical value. */ |
| 166 | + char * ptr = const_cast<char *>(content_length_ptr); |
| 167 | + for (; (*ptr != '\0') && !isDigit(*ptr); ptr++) { } |
| 168 | + /* Extract numerical value. */ |
| 169 | + String content_length_str; |
| 170 | + for (; isDigit(*ptr); ptr++) content_length_str += *ptr; |
| 171 | + int const content_length_val = atoi(content_length_str.c_str()); |
| 172 | + DEBUG_VERBOSE("%s: Length of OTA binary according to HTTP header = %d bytes", __FUNCTION__, content_length_val); |
| 173 | + |
| 174 | + /* Receive as many bytes as are indicated by the HTTP header - or die trying. */ |
| 175 | + int bytes_received = 0; |
| 176 | + bool is_http_data_timeout = false; |
| 177 | + for(unsigned long const start = millis(); bytes_received < content_length_val;) |
| 178 | + { |
| 179 | + is_http_data_timeout = (millis() - start) > AIOT_CONFIG_PORTENTA_C33_OTA_HTTP_DATA_RECEIVE_TIMEOUT_ms; |
| 180 | + if (is_http_data_timeout) break; |
| 181 | + |
| 182 | + if (client->available()) |
| 183 | + { |
| 184 | + char const c = client->read(); |
| 185 | + |
| 186 | + if (fwrite(&c, 1, sizeof(c), file) != sizeof(c)) |
| 187 | + { |
| 188 | + DEBUG_ERROR("%s: Writing of firmware image to flash failed", __FUNCTION__); |
| 189 | + fclose(file); |
| 190 | + return static_cast<int>(OTAError::PORTENTA_C33_ErrorWriteUpdateFile); |
| 191 | + } |
| 192 | + |
| 193 | + bytes_received++; |
| 194 | + } |
| 195 | + } |
| 196 | + |
| 197 | + if (bytes_received != content_length_val) { |
| 198 | + DEBUG_ERROR("%s: Error receiving HTTP data %s (%d bytes received, %d expected)", __FUNCTION__, is_http_data_timeout ? "(timeout)":"", bytes_received, content_length_val); |
| 199 | + fclose(file); |
| 200 | + return static_cast<int>(OTAError::PORTENTA_C33_HttpDataError); |
| 201 | + } |
| 202 | + |
| 203 | + DEBUG_INFO("%s: %d bytes received", __FUNCTION__, ftell(file)); |
| 204 | + fclose(file); |
| 205 | + |
| 206 | + /* Unmount the filesystem. */ |
| 207 | + if ((err = fs.unmount()) != 0) |
| 208 | + { |
| 209 | + DEBUG_ERROR("%s: fs.unmount() failed with %d", __FUNCTION__, err); |
| 210 | + return static_cast<int>(OTAError::PORTENTA_C33_ErrorUnmount); |
| 211 | + } |
| 212 | + |
| 213 | + return static_cast<int>(OTAError::None); |
| 214 | +} |
0 commit comments