- CitrineOS core extracted (CSMS OCPP 2.0.1) - OpenOCPP extracted (firmware OCPP 1.6J/2.0.1) - ShapeShifter library installed (pip install -e) - ShapeShifter specification extracted - EVerest extracted TODO updated with progress
476 lines
23 KiB
C++
476 lines
23 KiB
C++
// SPDX-License-Identifier: Apache-2.0
|
|
// Copyright Pionix GmbH and Contributors to EVerest
|
|
|
|
#include "systemImpl.hpp"
|
|
|
|
#include <chrono>
|
|
#include <cstdlib>
|
|
#include <fstream>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <utils/date.hpp>
|
|
|
|
#include <everest/run_application/run_application.hpp>
|
|
|
|
using namespace everest::run_application;
|
|
|
|
namespace module {
|
|
namespace main {
|
|
|
|
const std::string CONSTANTS = "constants.env";
|
|
const std::string DIAGNOSTICS_UPLOADER = "diagnostics_uploader.sh";
|
|
const std::string FIRMWARE_UPDATER = "firmware_updater.sh";
|
|
const std::string SIGNED_FIRMWARE_DOWNLOADER = "signed_firmware_downloader.sh";
|
|
const std::string SIGNED_FIRMWARE_INSTALLER = "signed_firmware_installer.sh";
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
// FIXME (aw): this function needs to be refactored into some kind of utility library
|
|
fs::path create_temp_file(const fs::path& dir, const std::string& prefix) {
|
|
const std::string fn_template = (dir / prefix).string() + "XXXXXX" + std::string(1, '\0');
|
|
std::vector<char> fn_template_buffer{fn_template.begin(), fn_template.end()};
|
|
|
|
// mkstemp needs to have at least 6 XXXXXX at the end and it will replace these
|
|
// with a valid file name
|
|
auto fd = mkstemp(fn_template_buffer.data());
|
|
|
|
if (fd == -1) {
|
|
EVLOG_error << "Failed to create temporary file at: " << fn_template;
|
|
return {};
|
|
}
|
|
|
|
// close the file descriptor
|
|
close(fd);
|
|
|
|
return fn_template_buffer.data();
|
|
}
|
|
|
|
void systemImpl::init() {
|
|
this->scripts_path = mod->info.paths.libexec;
|
|
this->log_upload_running = false;
|
|
this->firmware_download_running = false;
|
|
this->firmware_installation_running = false;
|
|
this->standard_firmware_update_running = false;
|
|
this->boot_reason_key = "ocpp_boot_reason";
|
|
}
|
|
|
|
void systemImpl::ready() {
|
|
}
|
|
|
|
void systemImpl::standard_firmware_update(const types::system::FirmwareUpdateRequest& firmware_update_request) {
|
|
|
|
this->standard_firmware_update_running = true;
|
|
EVLOG_info << "Starting firmware update";
|
|
// create temporary file
|
|
const auto date_time = Everest::Date::to_rfc3339(date::utc_clock::now());
|
|
|
|
const auto firmware_file_path = create_temp_file(fs::temp_directory_path(), "firmware-" + date_time);
|
|
|
|
if (firmware_file_path.empty()) {
|
|
EVLOG_error << "Firmware update ignored, cannot write temporary file.";
|
|
publish_firmware_update_status({types::system::FirmwareUpdateStatusEnum::DownloadFailed});
|
|
return;
|
|
}
|
|
|
|
const auto constants = this->scripts_path / CONSTANTS;
|
|
|
|
this->update_firmware_thread = std::thread([this, firmware_update_request, firmware_file_path, constants]() {
|
|
const auto firmware_updater = this->scripts_path / FIRMWARE_UPDATER;
|
|
|
|
const std::vector<std::string> args = {constants.string(), firmware_update_request.location,
|
|
firmware_file_path.string()};
|
|
int32_t retries = 0;
|
|
const auto total_retries = firmware_update_request.retries.value_or(this->mod->config.DefaultRetries);
|
|
const auto retry_interval =
|
|
firmware_update_request.retry_interval_s.value_or(this->mod->config.DefaultRetryInterval);
|
|
|
|
auto firmware_status_enum = types::system::FirmwareUpdateStatusEnum::DownloadFailed;
|
|
types::system::FirmwareUpdateStatus firmware_status;
|
|
firmware_status.request_id = -1;
|
|
firmware_status.firmware_update_status = firmware_status_enum;
|
|
|
|
while (firmware_status.firmware_update_status == types::system::FirmwareUpdateStatusEnum::DownloadFailed &&
|
|
retries < total_retries) {
|
|
retries += 1;
|
|
run_application(firmware_updater.string(), args, [this, &firmware_status](const std::string& output_line) {
|
|
firmware_status.firmware_update_status =
|
|
types::system::string_to_firmware_update_status_enum(output_line);
|
|
this->publish_firmware_update_status(firmware_status);
|
|
return CmdControl::Continue;
|
|
});
|
|
if (firmware_status.firmware_update_status == types::system::FirmwareUpdateStatusEnum::DownloadFailed &&
|
|
retries < total_retries) {
|
|
std::this_thread::sleep_for(std::chrono::seconds(retry_interval));
|
|
}
|
|
if (firmware_status.firmware_update_status == types::system::FirmwareUpdateStatusEnum::Installed and
|
|
!this->mod->r_store.empty()) {
|
|
this->mod->r_store.at(0)->call_store(boot_reason_key,
|
|
boot_reason_to_string(types::system::BootReason::FirmwareUpdate));
|
|
}
|
|
}
|
|
this->standard_firmware_update_running = false;
|
|
});
|
|
this->update_firmware_thread.detach();
|
|
}
|
|
|
|
types::system::UpdateFirmwareResponse
|
|
systemImpl::handle_standard_firmware_update(const types::system::FirmwareUpdateRequest& firmware_update_request) {
|
|
|
|
if (!this->standard_firmware_update_running) {
|
|
if (firmware_update_request.retrieve_timestamp.has_value() &&
|
|
Everest::Date::from_rfc3339(firmware_update_request.retrieve_timestamp.value()) > date::utc_clock::now()) {
|
|
const auto retrieve_timestamp =
|
|
Everest::Date::from_rfc3339(firmware_update_request.retrieve_timestamp.value());
|
|
this->standard_update_firmware_timer.at(
|
|
[this, retrieve_timestamp, firmware_update_request]() {
|
|
this->standard_firmware_update(firmware_update_request);
|
|
},
|
|
retrieve_timestamp);
|
|
EVLOG_info << "Download for firmware scheduled for: " << Everest::Date::to_rfc3339(retrieve_timestamp);
|
|
} else {
|
|
// start download immediately
|
|
this->update_firmware_thread = std::thread(
|
|
[this, firmware_update_request]() { this->standard_firmware_update(firmware_update_request); });
|
|
this->update_firmware_thread.detach();
|
|
}
|
|
return types::system::UpdateFirmwareResponse::Accepted;
|
|
} else {
|
|
EVLOG_info << "Not starting firmware update because firmware update process already running";
|
|
return types::system::UpdateFirmwareResponse::Rejected;
|
|
}
|
|
}
|
|
|
|
types::system::UpdateFirmwareResponse
|
|
systemImpl::handle_signed_fimware_update(const types::system::FirmwareUpdateRequest& firmware_update_request) {
|
|
|
|
if (!firmware_update_request.signing_certificate.has_value()) {
|
|
EVLOG_warning << "Signing certificate is missing in FirmwareUpdateRequest";
|
|
return types::system::UpdateFirmwareResponse::Rejected;
|
|
}
|
|
if (!firmware_update_request.signature.has_value()) {
|
|
EVLOG_warning << "Signature is missing in FirmwareUpdateRequest";
|
|
return types::system::UpdateFirmwareResponse::Rejected;
|
|
}
|
|
|
|
EVLOG_info << "Executing signed firmware update download callback";
|
|
|
|
if (firmware_update_request.retrieve_timestamp.has_value() &&
|
|
Everest::Date::from_rfc3339(firmware_update_request.retrieve_timestamp.value()) > date::utc_clock::now()) {
|
|
const auto retrieve_timestamp = Everest::Date::from_rfc3339(firmware_update_request.retrieve_timestamp.value());
|
|
this->signed_firmware_update_download_timer.at(
|
|
[this, retrieve_timestamp, firmware_update_request]() {
|
|
this->download_signed_firmware(firmware_update_request);
|
|
},
|
|
retrieve_timestamp);
|
|
EVLOG_info << "Download for firmware scheduled for: " << Everest::Date::to_rfc3339(retrieve_timestamp);
|
|
types::system::FirmwareUpdateStatus firmware_update_status;
|
|
firmware_update_status.request_id = firmware_update_request.request_id;
|
|
firmware_update_status.firmware_update_status = types::system::FirmwareUpdateStatusEnum::DownloadScheduled;
|
|
this->publish_firmware_update_status(firmware_update_status);
|
|
} else {
|
|
// start download immediately
|
|
this->update_firmware_thread =
|
|
std::thread([this, firmware_update_request]() { this->download_signed_firmware(firmware_update_request); });
|
|
this->update_firmware_thread.detach();
|
|
}
|
|
|
|
if (this->firmware_download_running) {
|
|
return types::system::UpdateFirmwareResponse::AcceptedCanceled;
|
|
} else if (this->firmware_installation_running) {
|
|
return types::system::UpdateFirmwareResponse::Rejected;
|
|
} else {
|
|
return types::system::UpdateFirmwareResponse::Accepted;
|
|
}
|
|
}
|
|
|
|
void systemImpl::download_signed_firmware(const types::system::FirmwareUpdateRequest& firmware_update_request) {
|
|
|
|
if (!firmware_update_request.signing_certificate.has_value()) {
|
|
EVLOG_warning << "Signing certificate is missing in FirmwareUpdateRequest";
|
|
this->publish_firmware_update_status(
|
|
{types::system::FirmwareUpdateStatusEnum::DownloadFailed, firmware_update_request.request_id});
|
|
return;
|
|
}
|
|
if (!firmware_update_request.signature.has_value()) {
|
|
EVLOG_warning << "Signature is missing in FirmwareUpdateRequest";
|
|
this->publish_firmware_update_status(
|
|
{types::system::FirmwareUpdateStatusEnum::DownloadFailed, firmware_update_request.request_id});
|
|
return;
|
|
}
|
|
|
|
if (this->firmware_download_running) {
|
|
EVLOG_info
|
|
<< "Received Firmware update request and firmware update already running - cancelling firmware update";
|
|
this->interrupt_firmware_download.exchange(true);
|
|
EVLOG_info << "Waiting for other firmware download to finish...";
|
|
std::unique_lock<std::mutex> lk(this->firmware_update_mutex);
|
|
this->firmware_update_cv.wait(lk, [this]() { return !this->firmware_download_running; });
|
|
EVLOG_info << "Previous Firmware download finished!";
|
|
}
|
|
|
|
std::lock_guard<std::mutex> lg(this->firmware_update_mutex);
|
|
EVLOG_info << "Starting Firmware update";
|
|
this->interrupt_firmware_download.exchange(false);
|
|
this->firmware_download_running = true;
|
|
|
|
// // create temporary file
|
|
const auto date_time = Everest::Date::to_rfc3339(date::utc_clock::now());
|
|
const auto firmware_file_path = create_temp_file(fs::temp_directory_path(), "signed_firmware-" + date_time);
|
|
|
|
const auto firmware_downloader = this->scripts_path / SIGNED_FIRMWARE_DOWNLOADER;
|
|
const auto constants = this->scripts_path / CONSTANTS;
|
|
|
|
const std::vector<std::string> download_args = {
|
|
constants.string(), firmware_update_request.location, firmware_file_path.string(),
|
|
firmware_update_request.signature.value(), firmware_update_request.signing_certificate.value()};
|
|
int32_t retries = 0;
|
|
const auto total_retries = firmware_update_request.retries.value_or(this->mod->config.DefaultRetries);
|
|
const auto retry_interval =
|
|
firmware_update_request.retry_interval_s.value_or(this->mod->config.DefaultRetryInterval);
|
|
|
|
auto firmware_status_enum = types::system::FirmwareUpdateStatusEnum::DownloadFailed;
|
|
types::system::FirmwareUpdateStatus firmware_status;
|
|
firmware_status.request_id = firmware_update_request.request_id;
|
|
firmware_status.firmware_update_status = firmware_status_enum;
|
|
|
|
while (firmware_status.firmware_update_status == types::system::FirmwareUpdateStatusEnum::DownloadFailed &&
|
|
retries < total_retries && !this->interrupt_firmware_download) {
|
|
run_application(
|
|
firmware_downloader.string(), download_args, [this, &firmware_status](const std::string& output_line) {
|
|
firmware_status.firmware_update_status =
|
|
types::system::string_to_firmware_update_status_enum(output_line);
|
|
this->publish_firmware_update_status(firmware_status);
|
|
if (this->interrupt_firmware_download) {
|
|
EVLOG_info << "Updating firmware was interrupted, terminating firmware update script, requestId: "
|
|
<< firmware_status.request_id;
|
|
return CmdControl::Terminate;
|
|
}
|
|
return CmdControl::Continue;
|
|
});
|
|
retries += 1;
|
|
if (firmware_status.firmware_update_status == types::system::FirmwareUpdateStatusEnum::DownloadFailed &&
|
|
retries < total_retries) {
|
|
std::this_thread::sleep_for(std::chrono::seconds(retry_interval));
|
|
}
|
|
}
|
|
if (firmware_status.firmware_update_status == types::system::FirmwareUpdateStatusEnum::SignatureVerified) {
|
|
this->initialize_firmware_installation(firmware_update_request, firmware_file_path);
|
|
}
|
|
|
|
this->firmware_download_running = false;
|
|
this->firmware_update_cv.notify_one();
|
|
EVLOG_info << "Firmware update thread finished";
|
|
}
|
|
|
|
void systemImpl::initialize_firmware_installation(const types::system::FirmwareUpdateRequest& firmware_update_request,
|
|
const fs::path& firmware_file_path) {
|
|
if (firmware_update_request.install_timestamp.has_value() &&
|
|
Everest::Date::from_rfc3339(firmware_update_request.install_timestamp.value()) > date::utc_clock::now()) {
|
|
const auto install_timestamp = Everest::Date::from_rfc3339(firmware_update_request.install_timestamp.value());
|
|
this->signed_firmware_update_install_timer.at(
|
|
[this, firmware_update_request, firmware_file_path]() {
|
|
this->install_signed_firmware(firmware_update_request, firmware_file_path);
|
|
},
|
|
install_timestamp);
|
|
EVLOG_info << "Installation for firmware scheduled for: " << Everest::Date::to_rfc3339(install_timestamp);
|
|
types::system::FirmwareUpdateStatus firmware_update_status;
|
|
firmware_update_status.request_id = firmware_update_request.request_id;
|
|
firmware_update_status.firmware_update_status = types::system::FirmwareUpdateStatusEnum::InstallScheduled;
|
|
this->publish_firmware_update_status(firmware_update_status);
|
|
} else {
|
|
// start installation immediately
|
|
this->update_firmware_thread = std::thread([this, firmware_update_request, firmware_file_path]() {
|
|
this->install_signed_firmware(firmware_update_request, firmware_file_path);
|
|
});
|
|
this->update_firmware_thread.detach();
|
|
}
|
|
}
|
|
|
|
void systemImpl::install_signed_firmware(const types::system::FirmwareUpdateRequest& firmware_update_request,
|
|
const fs::path& firmware_file_path) {
|
|
auto firmware_status_enum = types::system::FirmwareUpdateStatusEnum::Installing;
|
|
types::system::FirmwareUpdateStatus firmware_status;
|
|
firmware_status.request_id = firmware_update_request.request_id;
|
|
firmware_status.firmware_update_status = firmware_status_enum;
|
|
if (!this->firmware_installation_running) {
|
|
this->firmware_installation_running = true;
|
|
const auto firmware_installer = this->scripts_path / SIGNED_FIRMWARE_INSTALLER;
|
|
const auto constants = this->scripts_path / CONSTANTS;
|
|
const std::vector<std::string> install_args = {constants.string()};
|
|
run_application(firmware_installer.string(), install_args,
|
|
[this, &firmware_status](const std::string& output_line) {
|
|
firmware_status.firmware_update_status =
|
|
types::system::string_to_firmware_update_status_enum(output_line);
|
|
this->publish_firmware_update_status(firmware_status);
|
|
return CmdControl::Continue;
|
|
});
|
|
if (firmware_status.firmware_update_status == types::system::FirmwareUpdateStatusEnum::Installed) {
|
|
if (!this->mod->r_store.empty()) {
|
|
this->mod->r_store.at(0)->call_store(boot_reason_key,
|
|
boot_reason_to_string(types::system::BootReason::FirmwareUpdate));
|
|
}
|
|
|
|
auto reset_type = types::system::ResetType::Hard;
|
|
bool firmware_installation_running_copy = this->firmware_installation_running;
|
|
this->handle_reset(reset_type, firmware_installation_running_copy);
|
|
}
|
|
} else {
|
|
firmware_status.firmware_update_status = types::system::FirmwareUpdateStatusEnum::InstallationFailed;
|
|
this->publish_firmware_update_status(firmware_status);
|
|
}
|
|
}
|
|
|
|
types::system::UpdateFirmwareResponse
|
|
systemImpl::handle_update_firmware(types::system::FirmwareUpdateRequest& firmware_update_request) {
|
|
if (firmware_update_request.request_id == -1) {
|
|
return this->handle_standard_firmware_update(firmware_update_request);
|
|
} else {
|
|
return this->handle_signed_fimware_update(firmware_update_request);
|
|
}
|
|
};
|
|
|
|
void systemImpl::handle_allow_firmware_installation() {
|
|
// TODO: implement me
|
|
}
|
|
|
|
types::system::UploadLogsResponse
|
|
systemImpl::handle_upload_logs(types::system::UploadLogsRequest& upload_logs_request) {
|
|
|
|
types::system::UploadLogsResponse response;
|
|
|
|
if (this->log_upload_running) {
|
|
response.upload_logs_status = types::system::UploadLogsStatus::AcceptedCanceled;
|
|
} else {
|
|
response.upload_logs_status = types::system::UploadLogsStatus::Accepted;
|
|
}
|
|
|
|
const auto date_time = Everest::Date::to_rfc3339(date::utc_clock::now());
|
|
// TODO(piet): consider start time and end time
|
|
const auto diagnostics_file_path = create_temp_file(fs::temp_directory_path(), "diagnostics-" + date_time);
|
|
const auto diagnostics_file_name = diagnostics_file_path.filename().string();
|
|
|
|
response.file_name = diagnostics_file_name;
|
|
|
|
const auto fake_diagnostics_file = json({{"diagnostics", {{"key", "value"}}}});
|
|
std::ofstream diagnostics_file(diagnostics_file_path.c_str());
|
|
diagnostics_file << fake_diagnostics_file.dump();
|
|
|
|
this->upload_logs_thread = std::thread([this, upload_logs_request, diagnostics_file_name, diagnostics_file_path]() {
|
|
if (this->log_upload_running) {
|
|
EVLOG_info << "Received Log upload request and log upload already running - cancelling current upload";
|
|
this->interrupt_log_upload.exchange(true);
|
|
EVLOG_info << "Waiting for other log upload to finish...";
|
|
std::unique_lock<std::mutex> lk(this->log_upload_mutex);
|
|
this->log_upload_cv.wait(lk, [this]() { return !this->log_upload_running; });
|
|
EVLOG_info << "Previous Log upload finished!";
|
|
}
|
|
|
|
std::lock_guard<std::mutex> lg(this->log_upload_mutex);
|
|
EVLOG_info << "Starting upload of log file";
|
|
this->interrupt_log_upload.exchange(false);
|
|
this->log_upload_running = true;
|
|
const auto diagnostics_uploader = this->scripts_path / DIAGNOSTICS_UPLOADER;
|
|
const auto constants = this->scripts_path / CONSTANTS;
|
|
|
|
std::vector<std::string> args = {constants.string(), upload_logs_request.location, diagnostics_file_name,
|
|
diagnostics_file_path.string()};
|
|
bool uploaded = false;
|
|
int32_t retries = 0;
|
|
const auto total_retries = upload_logs_request.retries.value_or(this->mod->config.DefaultRetries);
|
|
const auto retry_interval =
|
|
upload_logs_request.retry_interval_s.value_or(this->mod->config.DefaultRetryInterval);
|
|
|
|
types::system::LogStatus log_status;
|
|
while (!uploaded && retries < total_retries && !this->interrupt_log_upload) {
|
|
retries += 1;
|
|
log_status.request_id = upload_logs_request.request_id.value_or(-1);
|
|
run_application(diagnostics_uploader.string(), args, [this, &log_status](const std::string& output_line) {
|
|
if (output_line == "Uploaded") {
|
|
log_status.log_status = types::system::string_to_log_status_enum(output_line);
|
|
} else if (output_line == "UploadFailure" || output_line == "PermissionDenied" ||
|
|
output_line == "BadMessage" || output_line == "NotSupportedOperation") {
|
|
log_status.log_status = types::system::LogStatusEnum::UploadFailure;
|
|
} else {
|
|
log_status.log_status = types::system::LogStatusEnum::Uploading;
|
|
}
|
|
this->publish_log_status(log_status);
|
|
if (this->interrupt_log_upload) {
|
|
return CmdControl::Terminate;
|
|
}
|
|
return CmdControl::Continue;
|
|
});
|
|
if (this->interrupt_log_upload) {
|
|
EVLOG_info << "Uploading Logs was interrupted, terminating upload script, requestId: "
|
|
<< log_status.request_id;
|
|
// N01.FR.20
|
|
log_status.log_status = types::system::LogStatusEnum::AcceptedCanceled;
|
|
this->publish_log_status(log_status);
|
|
} else if (log_status.log_status != types::system::LogStatusEnum::Uploaded && retries < total_retries) {
|
|
// command finished, but neither interrupted nor uploaded
|
|
std::this_thread::sleep_for(std::chrono::seconds(retry_interval));
|
|
} else {
|
|
uploaded = true;
|
|
}
|
|
}
|
|
this->log_upload_running = false;
|
|
this->log_upload_cv.notify_one();
|
|
EVLOG_info << "Log upload thread finished";
|
|
});
|
|
this->upload_logs_thread.detach();
|
|
|
|
return response;
|
|
};
|
|
|
|
bool systemImpl::handle_is_reset_allowed(types::system::ResetType& type) {
|
|
// Right now we dont want to reject a reset ever
|
|
return true;
|
|
}
|
|
|
|
void systemImpl::handle_reset(types::system::ResetType& type, bool& scheduled) {
|
|
// let the actual work be done by a worker thread, which can also delay it
|
|
// a little bit (if configured) to allow e.g. clean shutdown of communication
|
|
// channels in parallel when this call returns
|
|
std::thread([this, type, scheduled] {
|
|
EVLOG_info << "Reset request received: " << type << ", " << (scheduled ? "" : "not ") << "scheduled";
|
|
if (!this->mod->r_store.empty() and !this->mod->r_store.at(0)->call_exists(boot_reason_key)) {
|
|
this->mod->r_store.at(0)->call_store(boot_reason_key,
|
|
boot_reason_to_string(types::system::BootReason::RemoteReset));
|
|
}
|
|
|
|
std::this_thread::sleep_for(std::chrono::seconds(this->mod->config.ResetDelay));
|
|
|
|
if (type == types::system::ResetType::Soft) {
|
|
EVLOG_info << "Performing soft reset now.";
|
|
kill(getpid(), SIGINT);
|
|
} else {
|
|
EVLOG_info << "Performing hard reset now.";
|
|
kill(getpid(), SIGINT); // FIXME(piet): Define appropriate behavior for hard reset
|
|
}
|
|
}).detach();
|
|
}
|
|
|
|
bool systemImpl::handle_set_system_time(std::string& timestamp) {
|
|
// your code for cmd set_system_time goes here
|
|
return true;
|
|
};
|
|
|
|
types::system::BootReason systemImpl::handle_get_boot_reason() {
|
|
if (this->mod->r_store.empty()) {
|
|
return types::system::BootReason::PowerUp;
|
|
}
|
|
auto reason_variant = this->mod->r_store.at(0)->call_load(boot_reason_key);
|
|
auto* reason = std::get_if<std::string>(&reason_variant);
|
|
std::string final_reason{boot_reason_to_string(types::system::BootReason::PowerUp)};
|
|
if (reason != nullptr) {
|
|
final_reason = *reason;
|
|
}
|
|
this->mod->r_store.at(0)->call_delete(boot_reason_key);
|
|
return types::system::string_to_boot_reason(final_reason);
|
|
}
|
|
|
|
} // namespace main
|
|
} // namespace module
|