Skip to content

Commit

Permalink
OTA: chunked/timed download, improve code reuse
Browse files Browse the repository at this point in the history
  • Loading branch information
pennam committed Dec 11, 2024
1 parent 7e89f55 commit 74b1f17
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 103 deletions.
148 changes: 48 additions & 100 deletions src/ota/interface/OTAInterfaceDefault.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,59 +92,20 @@ OTACloudProcessInterface::State OTADefaultCloudProcessInterface::startOTA() {
}

OTACloudProcessInterface::State OTADefaultCloudProcessInterface::fetch() {
if(fetchMode == OtaFetchTime) {
return fetchTime();
} else {
return fetchChunk();
}
}

OTACloudProcessInterface::State OTADefaultCloudProcessInterface::fetchChunk() {
OTACloudProcessInterface::State res = Fetch;
int http_res = 0;
uint32_t start = millis();
char range[128] = {0};

/* stop connected client */
http_client->stop();

/* request chunk */
http_client->beginRequest();
http_res = http_client->get(context->parsed_url.path());

if(username != nullptr && password != nullptr) {
http_client->sendBasicAuth(username, password);
if(fetchMode == OtaFetchChunk) {
res = requestChunk();
}

size_t rangeSize = context->downloadedSize + context->maxChunkSize > context->contentLength ? context->contentLength - context->downloadedSize : context->maxChunkSize;
sprintf(range, "bytes=%d-%d", context->downloadedSize, context->downloadedSize + rangeSize);
DEBUG_VERBOSE("OTA downloading range: %s", range);
http_client->sendHeader("Range", range);
http_client->endRequest();

if(http_res == HTTP_ERROR_CONNECTION_FAILED) {
DEBUG_VERBOSE("OTA ERROR: http client error connecting to server \"%s:%d\"",
context->parsed_url.host(), context->parsed_url.port());
return ServerConnectErrorFail;
} else if(http_res == HTTP_ERROR_TIMED_OUT) {
DEBUG_VERBOSE("OTA ERROR: http client timeout \"%s\"", OTACloudProcessInterface::context->url);
return OtaHeaderTimeoutFail;
} else if(http_res != HTTP_SUCCESS) {
DEBUG_VERBOSE("OTA ERROR: http client returned %d on get \"%s\"", res, OTACloudProcessInterface::context->url);
return OtaDownloadFail;
}

int statusCode = http_client->responseStatusCode();
context->downloadedChunkSize = 0;
context->downloadedChunkStartTime = millis();

if(statusCode != 206) {
DEBUG_VERBOSE("OTA ERROR: get response on \"%s\" returned status %d", OTACloudProcessInterface::context->url, statusCode);
return HttpResponseFail;
if(res != Fetch) {
goto exit;
}

http_client->skipResponseHeaders();

/* download chunk */
context->downloadedChunkSize = 0;
/* download chunked or timed */
do {
if(!http_client->connected()) {
res = OtaDownloadFail;
Expand All @@ -157,7 +118,7 @@ OTACloudProcessInterface::State OTADefaultCloudProcessInterface::fetchChunk() {
continue;
}

http_res = http_client->read(context->buffer, context->bufLen);
int http_res = http_client->read(context->buffer, context->bufLen);

if(http_res < 0) {
DEBUG_VERBOSE("OTA ERROR: Download read error %d", http_res);
Expand All @@ -175,8 +136,7 @@ OTACloudProcessInterface::State OTADefaultCloudProcessInterface::fetchChunk() {

context->downloadedChunkSize += http_res;

} while((context->downloadState == OtaDownloadFile || context->downloadState == OtaDownloadHeader) &&
(context->downloadedChunkSize < rangeSize));
} while(context->downloadState < OtaDownloadCompleted && fetchMore());

// TODO verify that the information present in the ota header match the info in context
if(context->downloadState == OtaDownloadCompleted) {
Expand Down Expand Up @@ -209,70 +169,58 @@ OTACloudProcessInterface::State OTADefaultCloudProcessInterface::fetchChunk() {
return res;
}

OTACloudProcessInterface::State OTADefaultCloudProcessInterface::fetchTime() {
OTACloudProcessInterface::State res = Fetch;
OTACloudProcessInterface::State OTADefaultCloudProcessInterface::requestChunk() {
int http_res = 0;
uint32_t start = millis();
char range[128] = {0};

do {
if(!http_client->connected()) {
res = OtaDownloadFail;
goto exit;
}
/* stop connected client */
http_client->stop();

if(http_client->available() == 0) {
/* Avoid tight loop and allow yield */
delay(1);
continue;
}
/* request chunk */
http_client->beginRequest();
http_res = http_client->get(context->parsed_url.path());

http_res = http_client->read(context->buffer, context->bufLen);
if(username != nullptr && password != nullptr) {
http_client->sendBasicAuth(username, password);
}

if(http_res < 0) {
DEBUG_VERBOSE("OTA ERROR: Download read error %d", http_res);
res = OtaDownloadFail;
goto exit;
}
size_t rangeSize = context->downloadedSize + maxChunkSize > context->contentLength ? context->contentLength - context->downloadedSize : maxChunkSize;
sprintf(range, "bytes=%d-%d", context->downloadedSize, context->downloadedSize + rangeSize);
DEBUG_VERBOSE("OTA downloading range: %s", range);
http_client->sendHeader("Range", range);
http_client->endRequest();

parseOta(context->buffer, http_res);
if(http_res == HTTP_ERROR_CONNECTION_FAILED) {
DEBUG_VERBOSE("OTA ERROR: http client error connecting to server \"%s:%d\"",
context->parsed_url.host(), context->parsed_url.port());
return ServerConnectErrorFail;
} else if(http_res == HTTP_ERROR_TIMED_OUT) {
DEBUG_VERBOSE("OTA ERROR: http client timeout \"%s\"", OTACloudProcessInterface::context->url);
return OtaHeaderTimeoutFail;
} else if(http_res != HTTP_SUCCESS) {
DEBUG_VERBOSE("OTA ERROR: http client returned %d on get \"%s\"", http_res, OTACloudProcessInterface::context->url);
return OtaDownloadFail;
}

if(context->writeError) {
DEBUG_VERBOSE("OTA ERROR: File write error");
res = ErrorWriteUpdateFileFail;
goto exit;
}
} while((context->downloadState == OtaDownloadFile || context->downloadState == OtaDownloadHeader) &&
millis() - start < downloadTime);
int statusCode = http_client->responseStatusCode();

// TODO verify that the information present in the ota header match the info in context
if(context->downloadState == OtaDownloadCompleted) {
// Verify that the downloaded file size is matching the expected size ??
// this could distinguish between consistency of the downloaded bytes and filesize
if(statusCode != 206) {
DEBUG_VERBOSE("OTA ERROR: get response on \"%s\" returned status %d", OTACloudProcessInterface::context->url, statusCode);
return HttpResponseFail;
}

// validate CRC
context->calculatedCrc32 ^= 0xFFFFFFFF; // finalize CRC
if(context->header.header.crc32 == context->calculatedCrc32) {
DEBUG_VERBOSE("Ota download completed successfully");
res = FlashOTA;
} else {
res = OtaHeaderCrcFail;
}
} else if(context->downloadState == OtaDownloadError) {
DEBUG_VERBOSE("OTA ERROR: OtaDownloadError");
http_client->skipResponseHeaders();

res = OtaDownloadFail;
} else if(context->downloadState == OtaDownloadMagicNumberMismatch) {
DEBUG_VERBOSE("OTA ERROR: Magic number mismatch");
res = OtaHeaderMagicNumberFail;
}
return Fetch;
}

exit:
if(res != Fetch) {
http_client->stop(); // close the connection
delete http_client;
http_client = nullptr;
bool OTADefaultCloudProcessInterface::fetchMore() {
if (fetchMode == OtaFetchChunk) {
return context->downloadedChunkSize < maxChunkSize;
} else {
return (millis() - context->downloadedChunkStartTime) < downloadTime;
}
return res;
}

void OTADefaultCloudProcessInterface::parseOta(uint8_t* buffer, size_t bufLen) {
Expand Down
8 changes: 5 additions & 3 deletions src/ota/interface/OTAInterfaceDefault.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ class OTADefaultCloudProcessInterface: public OTACloudProcessInterface {

private:
void parseOta(uint8_t* buffer, size_t bufLen);
State fetchTime();
State fetchChunk();
State requestChunk();
bool fetchMore();

Client* client;
HttpClient* http_client;
Expand All @@ -63,6 +63,8 @@ class OTADefaultCloudProcessInterface: public OTACloudProcessInterface {
// This mitigate the issues arising from tasks run in main loop that are using all the computing time
static constexpr uint32_t downloadTime = 2000;

static constexpr size_t maxChunkSize = 1024 * 10;

enum OTADownloadState: uint8_t {
OtaDownloadHeader,
OtaDownloadFile,
Expand All @@ -87,8 +89,8 @@ class OTADefaultCloudProcessInterface: public OTACloudProcessInterface {
uint32_t contentLength;
bool writeError;

uint32_t downloadedChunkStartTime;
uint32_t downloadedChunkSize;
static constexpr size_t maxChunkSize = 1024 * 10;

// LZSS decoder
LZSSDecoder decoder;
Expand Down

0 comments on commit 74b1f17

Please sign in to comment.