Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions components/ocs_http/irequest.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
#pragma once

#include <cstdint>
#include <sys/types.h>

#include "ocs_status/code.h"

namespace ocs {
namespace http {

Expand All @@ -26,6 +30,17 @@ class IRequest {

//! Get the request method.
virtual Method get_method() const = 0;

//! Return request content length.
virtual size_t get_content_length() const = 0;

//! Read @p len bytes of data into @p buf.
//!
//! @params
//! - @p read_size - number of bytes read.
//! - @p buf - buffer to read data.
//! - @p buf_size - buffer size, should be at least @p buf_size bytes long.
virtual status::StatusCode read(size_t& read_size, uint8_t* buf, size_t buf_size) = 0;
};

} // namespace http
Expand Down
19 changes: 19 additions & 0 deletions components/ocs_http/target_esp32/request.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,24 @@ IRequest::Method Request::get_method() const {
return IRequest::Method::Unsupported;
}

size_t Request::get_content_length() const {
return req_.content_len;
}

status::StatusCode Request::read(size_t& read_size, uint8_t* buf, size_t buf_size) {
const auto ret = httpd_req_recv(&req_, reinterpret_cast<char*>(buf), buf_size);
if (ret <= 0) {
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
return status::StatusCode::Timeout;
}

return status::StatusCode::Error;
}

read_size = ret;

return status::StatusCode::OK;
}

} // namespace http
} // namespace ocs
6 changes: 6 additions & 0 deletions components/ocs_http/target_esp32/request.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ class Request : public IRequest, private core::NonCopyable<> {
//! Return method of the underlying request.
Method get_method() const override;

//! Return request content length.
size_t get_content_length() const override;

//! Read request data into @p buf.
status::StatusCode read(size_t& read_size, uint8_t* buf, size_t buf_size) override;

private:
httpd_req_t& req_;
};
Expand Down
1 change: 1 addition & 0 deletions components/ocs_pipeline/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ idf_component_register(
"httpserver/sta_network_handler.cpp"
"httpserver/analog_config_store_handler.cpp"
"httpserver/locate_handler.cpp"
"httpserver/update_handler.cpp"

"jsonfmt/data_pipeline.cpp"
"jsonfmt/ap_network_formatter.cpp"
Expand Down
170 changes: 170 additions & 0 deletions components/ocs_pipeline/httpserver/update_handler.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/*
* SPDX-FileCopyrightText: 2025 Tendry Lab
* SPDX-License-Identifier: Apache-2.0
*/

#include <algorithm>
#include <charconv>
#include <cstdio>
#include <cstring>
#include <string>

#include "ocs_algo/response_ops.h"
#include "ocs_core/lock_guard.h"
#include "ocs_core/log.h"
#include "ocs_pipeline/httpserver/update_handler.h"
#include "ocs_status/code_to_str.h"

namespace ocs {
namespace pipeline {
namespace httpserver {

namespace {

template <typename T>
status::StatusCode
parse_number(T& value, const char* key, const algo::UriOps::Values& values) {
const auto it = values.find(key);
if (it == values.end()) {
return status::StatusCode::InvalidArg;
}

const auto [_, ec] =
std::from_chars(it->second.data(), it->second.data() + it->second.size(), value);
if (ec != std::errc()) {
return status::StatusCode::InvalidArg;
}

if (!value) {
return status::StatusCode::InvalidArg;
}

return status::StatusCode::OK;
}

const char* log_tag = "update_hanlder";

} // namespace

UpdateHandler::UpdateHandler(system::IUpdater& updater,
scheduler::ITask& reboot_task,
size_t buffer_size)
: buffer_size_(buffer_size)
, updater_(updater)
, reboot_task_(reboot_task) {
}

status::StatusCode UpdateHandler::serve_http(http::IResponseWriter& w,
http::IRequest& r) {
core::LockGuard lock(mu_);

auto code = begin_(r);
if (code != status::StatusCode::OK) {
return code;
}

code = write_(r);
if (code != status::StatusCode::OK) {
ocs_loge(log_tag, "failed to write firmware: %s", status::code_to_str(code));

if (const auto c = updater_.end(); c != status::StatusCode::OK) {
ocs_loge(log_tag, "failed to end update process on write failure: %s",
status::code_to_str(c));
}

return code;
}

code = updater_.commit();
if (code != status::StatusCode::OK) {
ocs_loge(log_tag, "failed to commit written firmware: %s",
status::code_to_str(code));

if (const auto c = updater_.end(); c != status::StatusCode::OK) {
ocs_loge(log_tag, "failed to end update process on commit failure: %s",
status::code_to_str(c));
}

return code;
}

code = updater_.end();
if (code != status::StatusCode::OK) {
ocs_loge(log_tag, "failed to end update process: %s", status::code_to_str(code));

return code;
}

code = algo::ResponseOps::write_text(w, "OK");
if (code != status::StatusCode::OK) {
return code;
}

return reboot_task_.run();
}

status::StatusCode UpdateHandler::begin_(http::IRequest& r) {
const auto values = algo::UriOps::parse_query(r.get_uri());

size_t total_size = 0;
auto code = parse_number(total_size, "total_size", values);
if (code != status::StatusCode::OK) {
return code;
}

uint32_t crc32 = 0;
code = parse_number(crc32, "crc32", values);
if (code != status::StatusCode::OK) {
return code;
}

code = updater_.begin(total_size, crc32);
if (code != status::StatusCode::OK) {
return code;
}

return status::StatusCode::OK;
}

status::StatusCode UpdateHandler::write_(http::IRequest& r) {
configASSERT(!buffer_);

buffer_.reset(new (std::nothrow) uint8_t[buffer_size_]);
if (!buffer_) {
return status::StatusCode::NoMem;
}

status::StatusCode code = status::StatusCode::OK;

auto remaining = r.get_content_length();

while (remaining) {
size_t read_size = 0;

code = r.read(read_size, buffer_.get(), std::min(remaining, buffer_size_));
if (code != status::StatusCode::OK) {
if (code == status::StatusCode::Timeout) {
continue;
}

break;
}

code = updater_.write(buffer_.get(), read_size);
if (code != status::StatusCode::OK) {
break;
}

memset(buffer_.get(), 0, read_size);

remaining -= read_size;
}

buffer_ = nullptr;

return code;
}

} // namespace httpserver
} // namespace pipeline
} // namespace ocs
54 changes: 54 additions & 0 deletions components/ocs_pipeline/httpserver/update_handler.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* SPDX-FileCopyrightText: 2025 Tendry Lab
* SPDX-License-Identifier: Apache-2.0
*/

#pragma once

#include <memory>

#include "ocs_algo/uri_ops.h"
#include "ocs_core/noncopyable.h"
#include "ocs_core/static_mutex.h"
#include "ocs_http/ihandler.h"
#include "ocs_http/irequest.h"
#include "ocs_http/iresponse_writer.h"
#include "ocs_scheduler/itask.h"
#include "ocs_system/iupdater.h"

namespace ocs {
namespace pipeline {
namespace httpserver {

class UpdateHandler : public http::IHandler, private core::NonCopyable<> {
public:
//! Initialize.
//!
//! @params
//! - @p updater to perform the firmware update process.
//! - @p reboot_task to reboot the system once the firmware update process is
//! finished.
//! - @p buffer_size - buffer size to read chunks of firmware from HTTP connection.
UpdateHandler(system::IUpdater& updater,
scheduler::ITask& reboot_task,
size_t buffer_size);

// Update device over HTTP.
status::StatusCode serve_http(http::IResponseWriter& w, http::IRequest& r) override;

private:
status::StatusCode begin_(http::IRequest& r);
status::StatusCode write_(http::IRequest& r);

const size_t buffer_size_ { 0 };

core::StaticMutex mu_;
system::IUpdater& updater_;
scheduler::ITask& reboot_task_;

std::unique_ptr<uint8_t[]> buffer_;
};

} // namespace httpserver
} // namespace pipeline
} // namespace ocs
3 changes: 3 additions & 0 deletions components/ocs_system/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@ idf_component_register(
"target_esp32/platform_builder.cpp"
"target_esp32/randomizer.cpp"
"target_esp32/high_resolution_timer.cpp"
"target_esp32/le_crc32_calculator.cpp"
"target_esp32/updater.cpp"

REQUIRES
"esp_timer"
"driver"
"ocs_security"
"app_update"

INCLUDE_DIRS
".."
Expand Down
29 changes: 29 additions & 0 deletions components/ocs_system/icrc32_calculator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* SPDX-FileCopyrightText: 2025 Tendry Lab
* SPDX-License-Identifier: Apache-2.0
*/

#pragma once

#include <cstddef>
#include <cstdint>

namespace ocs {
namespace system {

class ICrc32Calculator {
public:
//! Destroy.
virtual ~ICrc32Calculator() = default;

//! Calculate CRC32.
//!
//! @params
//! - @p crc - seed value, or the previous crc32 value if computing incrementally.
//! - @p buf - buffer over which CRC is run, should be at least @p len bytes long.
//! - @p len - buffer size, in bytes.
virtual uint32_t calculate(uint32_t crc, const uint8_t* buf, size_t len) = 0;
};

} // namespace system
} // namespace ocs
44 changes: 44 additions & 0 deletions components/ocs_system/iupdater.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* SPDX-FileCopyrightText: 2025 Tendry Lab
* SPDX-License-Identifier: Apache-2.0
*/

#pragma once

#include <cstddef>
#include <cstdint>

#include "ocs_status/code.h"

namespace ocs {
namespace system {

//! Firmware updater.
class IUpdater {
public:
//! Destroy.
virtual ~IUpdater() = default;

//! Begin firmware update.
//!
//! @params
//! - @p total_size - total firmware size, in bytes.
//! - @p crc32 - CRC32 to verify data integrity.
virtual status::StatusCode begin(size_t total_size, uint32_t crc32) = 0;

//! Write firmware data.
//!
//! @params
//! - @p buf - buffer with firmware, should be at least @p len bytes long.
//! - @p len - buffer size, in bytes.
virtual status::StatusCode write(const uint8_t* buf, size_t len) = 0;

//! Commit written firmware data.
virtual status::StatusCode commit() = 0;

//! End firmware update.
virtual status::StatusCode end() = 0;
};

} // namespace system
} // namespace ocs
Loading