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