1 #include "sotauptaneclient.h"
8 #include "campaign/campaign.h"
9 #include "crypto/crypto.h"
10 #include "crypto/keymanager.h"
11 #include "initializer.h"
12 #include "logging/logging.h"
13 #include "uptane/exceptions.h"
15 #include "utilities/fault_injection.h"
16 #include "utilities/utils.h"
18 static void report_progress_cb(event::Channel *channel,
const Uptane::Target &target,
const std::string &description,
19 unsigned int progress) {
20 if (channel ==
nullptr) {
23 auto event = std::make_shared<event::DownloadProgressReport>(target, description, progress);
27 void SotaUptaneClient::addSecondary(
const std::shared_ptr<Uptane::SecondaryInterface> &sec) {
31 if (!storage->loadSecondaryInfo(serial, &info) || info.type ==
"" || info.pub_key.Type() == KeyType::kUnknown) {
33 info.hw_id = sec->getHwId();
34 info.type = sec->Type();
36 if (p.Type() != KeyType::kUnknown) {
39 storage->saveSecondaryInfo(info.serial, info.type, info.pub_key);
42 if (storage->loadEcuRegistered()) {
44 storage->loadEcuSerials(&serials);
46 if (std::find_if(serials.cbegin(), serials.cend(), secondary_comp) == serials.cend()) {
47 throw std::logic_error(
"Adding new Secondaries to a provisioned device is not implemented yet");
51 const auto map_it = secondaries.find(serial);
52 if (map_it != secondaries.end()) {
53 throw std::runtime_error(std::string(
"Multiple Secondaries found with the same serial: ") + serial.ToString());
56 secondaries.emplace(serial, sec);
59 bool SotaUptaneClient::isInstalledOnPrimary(
const Uptane::Target &target) {
60 if (target.ecus().find(primaryEcuSerial()) != target.ecus().end()) {
61 return target.MatchTarget(package_manager_->getCurrent());
66 std::vector<Uptane::Target> SotaUptaneClient::findForEcu(
const std::vector<Uptane::Target> &targets,
68 std::vector<Uptane::Target>
result;
69 for (
auto it = targets.begin(); it != targets.end(); ++it) {
70 if (it->ecus().find(ecu_id) != it->ecus().end()) {
78 LOG_INFO <<
"Installing package using " << package_manager_->name() <<
" package manager";
80 return package_manager_->install(target);
81 }
catch (std::exception &ex) {
82 LOG_ERROR <<
"Installation failed: " << ex.what();
87 void SotaUptaneClient::finalizeAfterReboot() {
90 if (!hasPendingUpdates()) {
91 LOG_DEBUG <<
"No pending updates, continuing with initialization";
95 LOG_INFO <<
"Checking for a pending update to apply for Primary ECU";
98 boost::optional<Uptane::Target> pending_target;
99 storage->loadInstalledVersions(primary_ecu_serial.ToString(),
nullptr, &pending_target);
101 if (!pending_target) {
102 LOG_ERROR <<
"No pending update for Primary ECU found, continuing with initialization";
106 LOG_INFO <<
"Pending update for Primary ECU was found, trying to apply it...";
110 if (install_res.result_code == data::ResultCode::Numeric::kNeedCompletion) {
111 LOG_INFO <<
"Pending update for Primary ECU was not applied because reboot was not detected, "
112 "continuing with initialization";
116 storage->saveEcuInstallationResult(primary_ecu_serial, install_res);
118 const std::string correlation_id = pending_target->correlation_id();
119 if (install_res.success) {
120 storage->saveInstalledVersion(primary_ecu_serial.ToString(), *pending_target, InstalledVersionUpdateMode::kCurrent);
122 report_queue->enqueue(std_::make_unique<EcuInstallationCompletedReport>(primary_ecu_serial, correlation_id,
true));
125 storage->saveInstalledVersion(primary_ecu_serial.ToString(), *pending_target, InstalledVersionUpdateMode::kNone);
126 report_queue->enqueue(std_::make_unique<EcuInstallationCompletedReport>(primary_ecu_serial, correlation_id,
false));
129 director_repo.dropTargets(*storage);
132 std::string raw_report;
133 computeDeviceInstallationResult(&ir, &raw_report);
134 storage->storeDeviceInstallationResult(ir, raw_report, correlation_id);
150 storage->saveInstalledVersion(ecu_serial.ToString(), target, InstalledVersionUpdateMode::kNone);
152 result = PackageInstall(target);
153 if (
result.result_code.num_code == data::ResultCode::Numeric::kOk) {
155 storage->saveInstalledVersion(ecu_serial.ToString(), target, InstalledVersionUpdateMode::kCurrent);
156 }
else if (
result.result_code.num_code == data::ResultCode::Numeric::kNeedCompletion) {
158 storage->saveInstalledVersion(ecu_serial.ToString(), target, InstalledVersionUpdateMode::kPending);
160 storage->saveEcuInstallationResult(ecu_serial,
result);
164 void SotaUptaneClient::reportHwInfo(
const Json::Value &custom_hwinfo) {
165 Json::Value system_info;
167 if (custom_hwinfo.empty()) {
168 system_info = Utils::getHardwareInfo();
169 if (system_info.empty()) {
170 LOG_WARNING <<
"Unable to fetch hardware information from host system.";
175 const Json::Value &hw_info = custom_hwinfo.empty() ? system_info : custom_hwinfo;
176 if (hw_info != last_hw_info_reported) {
177 if (http->put(config.tls.server +
"/system_info", hw_info).isOk()) {
178 last_hw_info_reported = hw_info;
183 void SotaUptaneClient::reportInstalledPackages() {
184 http->put(config.tls.server +
"/core/installed", package_manager_->getInstalledPackages());
187 void SotaUptaneClient::reportNetworkInfo() {
189 LOG_DEBUG <<
"Reporting network information";
190 Json::Value network_info = Utils::getNetworkInfo();
191 if (network_info != last_network_info_reported) {
192 HttpResponse response = http->put(config.tls.server +
"/system_info/network", network_info);
193 if (response.isOk()) {
194 last_network_info_reported = network_info;
199 LOG_DEBUG <<
"Not reporting network information because telemetry is disabled";
203 void SotaUptaneClient::reportAktualizrConfiguration() {
204 if (!config.telemetry.report_config) {
205 LOG_DEBUG <<
"Not reporting network information because telemetry is disabled";
209 LOG_DEBUG <<
"Reporting libaktualizr configuration";
210 std::stringstream conf_ss;
211 config.writeToStream(conf_ss);
212 http->post(config.tls.server +
"/system_info/config",
"application/toml", conf_ss.str());
215 Json::Value SotaUptaneClient::AssembleManifest() {
216 Json::Value manifest;
218 manifest[
"primary_ecu_serial"] = primary_ecu_serial.ToString();
221 Json::Value version_manifest;
223 Json::Value primary_manifest = uptane_manifest->assembleManifest(package_manager_->getCurrent());
224 std::vector<std::pair<Uptane::EcuSerial, int64_t>> ecu_cnt;
225 std::string report_counter;
226 if (!storage->loadEcuReportCounter(&ecu_cnt) || (ecu_cnt.size() == 0)) {
227 LOG_ERROR <<
"No ECU version report counter, please check the database!";
230 report_counter = std::to_string(ecu_cnt[0].second + 1);
231 storage->saveEcuReportCounter(ecu_cnt[0].first, ecu_cnt[0].second + 1);
233 version_manifest[primary_ecu_serial.ToString()] = uptane_manifest->sign(primary_manifest, report_counter);
235 for (
auto it = secondaries.begin(); it != secondaries.end(); it++) {
239 bool from_cache =
false;
240 if (secmanifest == Json::Value()) {
243 if (storage->loadCachedEcuManifest(ecu_serial, &cached)) {
244 LOG_WARNING <<
"Could not reach Secondary " << ecu_serial <<
", sending a cached version of its manifest";
245 secmanifest = Utils::parseJSON(cached);
248 LOG_ERROR <<
"Could not fetch a valid Secondary manifest from cache";
252 if (secmanifest.verifySignature(it->second->getPublicKey())) {
253 version_manifest[ecu_serial.ToString()] = secmanifest;
255 storage->storeCachedEcuManifest(ecu_serial, Utils::jsonToCanonicalStr(secmanifest));
259 LOG_ERROR <<
"Secondary manifest is corrupted or not signed, or signature is invalid manifest: " << secmanifest;
262 manifest[
"ecu_version_manifests"] = version_manifest;
265 Json::Value installation_report;
268 std::string raw_report;
269 std::string correlation_id;
270 bool has_results = storage->loadDeviceInstallationResult(&dev_result, &raw_report, &correlation_id);
272 if (!(dev_result.isSuccess() || dev_result.needCompletion())) {
273 director_repo.dropTargets(*storage);
276 installation_report[
"result"] = dev_result.toJson();
277 installation_report[
"raw_report"] = raw_report;
278 installation_report[
"correlation_id"] = correlation_id;
279 installation_report[
"items"] = Json::arrayValue;
281 std::vector<std::pair<Uptane::EcuSerial, data::InstallationResult>> ecu_results;
282 storage->loadEcuInstallationResults(&ecu_results);
283 for (
const auto &r : ecu_results) {
288 item[
"ecu"] = serial.ToString();
289 item[
"result"] = res.toJson();
291 installation_report[
"items"].append(item);
294 manifest[
"installation_report"][
"content_type"] =
"application/vnd.com.here.otac.installationReport.v1";
295 manifest[
"installation_report"][
"report"] = installation_report;
297 LOG_DEBUG <<
"No installation result to report in manifest";
303 bool SotaUptaneClient::hasPendingUpdates()
const {
return storage->hasPendingInstall(); }
305 void SotaUptaneClient::initialize() {
306 LOG_DEBUG <<
"Checking if device is provisioned...";
307 auto keys = std::make_shared<KeyManager>(storage, config.keymanagerConfig());
309 Initializer initializer(config.provision, storage, http, *keys, secondaries);
313 if (!storage->loadEcuSerials(&serials) || serials.size() == 0) {
314 throw std::runtime_error(
"Unable to load ECU serials after device registration.");
317 uptane_manifest = std::make_shared<Uptane::ManifestIssuer>(keys, serials[0].first);
318 primary_ecu_serial_ = serials[0].first;
319 primary_ecu_hw_id_ = serials[0].second;
320 LOG_INFO <<
"Primary ECU serial: " << primary_ecu_serial_ <<
" with hardware ID: " << primary_ecu_hw_id_;
324 std::string device_id;
325 if (!storage->loadDeviceId(&device_id)) {
326 throw std::runtime_error(
"Unable to load device ID after device registration.");
328 LOG_INFO <<
"Device ID: " << device_id;
329 LOG_INFO <<
"Device Gateway URL: " << config.tls.server;
333 std::string not_before;
334 std::string not_after;
335 keys->getCertInfo(&subject, &issuer, ¬_before, ¬_after);
336 LOG_INFO <<
"Certificate subject: " << subject;
337 LOG_INFO <<
"Certificate issuer: " << issuer;
338 LOG_INFO <<
"Certificate valid from: " << not_before <<
" until: " << not_after;
340 LOG_DEBUG <<
"... provisioned OK";
341 finalizeAfterReboot();
344 void SotaUptaneClient::updateDirectorMeta() {
346 director_repo.updateMeta(*storage, *uptane_fetcher);
347 }
catch (
const std::exception &e) {
348 LOG_ERROR <<
"Director metadata update failed: " << e.what();
353 void SotaUptaneClient::updateImageMeta() {
355 image_repo.updateMeta(*storage, *uptane_fetcher);
356 }
catch (
const std::exception &e) {
357 LOG_ERROR <<
"Failed to update Image repo metadata: " << e.what();
362 void SotaUptaneClient::checkDirectorMetaOffline() {
364 director_repo.checkMetaOffline(*storage);
365 }
catch (
const std::exception &e) {
366 LOG_ERROR <<
"Failed to check Director metadata: " << e.what();
371 void SotaUptaneClient::checkImageMetaOffline() {
373 image_repo.checkMetaOffline(*storage);
374 }
catch (
const std::exception &e) {
375 LOG_ERROR <<
"Failed to check Image repo metadata: " << e.what();
380 std::string *raw_installation_report)
const {
383 std::string raw_ir =
"Installation succesful";
386 std::vector<std::pair<Uptane::EcuSerial, data::InstallationResult>> ecu_results;
388 if (!storage->loadEcuInstallationResults(&ecu_results)) {
391 "Unable to get installation results from ecus");
392 raw_ir =
"Failed to load ECUs' installation result";
397 std::string result_code_err_str;
399 for (
const auto &r : ecu_results) {
400 auto ecu_serial = r.first;
401 auto installation_res = r.second;
403 auto hw_id = ecuHwId(ecu_serial);
408 "Unable to get installation results from ECUs");
410 raw_ir =
"Couldn't find any ECU with the given serial: " + ecu_serial.ToString();
415 if (installation_res.needCompletion()) {
417 device_installation_result =
419 "ECU needs completion/finalization to be installed: " + ecu_serial.ToString());
420 raw_ir =
"ECU needs completion/finalization to be installed: " + ecu_serial.ToString();
427 if (!installation_res.isSuccess()) {
428 std::string ecu_code_str = (*hw_id).ToString() +
":" + installation_res.result_code.toString();
429 result_code_err_str += (result_code_err_str !=
"" ?
"|" :
"") + ecu_code_str;
433 if (!result_code_err_str.empty()) {
435 device_installation_result =
437 "Installation failed on at least one of ECUs");
438 raw_ir =
"Installation failed on at least one of ECUs";
446 *
result = device_installation_result;
449 if (raw_installation_report !=
nullptr) {
450 *raw_installation_report = raw_ir;
454 void SotaUptaneClient::getNewTargets(std::vector<Uptane::Target> *new_targets,
unsigned int *ecus_count) {
455 std::vector<Uptane::Target> targets = director_repo.getTargets().targets;
457 if (ecus_count !=
nullptr) {
462 for (
const auto &ecu : target.ecus()) {
470 auto hw_id_known = ecuHwId(ecu_serial);
472 LOG_ERROR <<
"Unknown ECU ID in Director Targets metadata: " << ecu_serial.ToString();
476 if (*hw_id_known != hw_id) {
477 LOG_ERROR <<
"Wrong hardware identifier for ECU " << ecu_serial.ToString();
481 boost::optional<Uptane::Target> current_version;
482 if (!storage->loadInstalledVersions(ecu_serial.ToString(), ¤t_version,
nullptr)) {
483 LOG_WARNING <<
"Could not load currently installed version for ECU ID: " << ecu_serial.ToString();
487 if (!current_version) {
488 LOG_WARNING <<
"Current version for ECU ID: " << ecu_serial.ToString() <<
" is unknown";
490 }
else if (current_version->filename() != target.filename()) {
494 if (primary_ecu_serial == ecu_serial) {
496 (config.pacman.type == PACKAGE_MANAGER_OSTREE || config.pacman.type == PACKAGE_MANAGER_OSTREEDOCKERAPP)) {
497 LOG_ERROR <<
"Cannot install a non-OSTree package on an OSTree system";
502 if (is_new && ecus_count !=
nullptr) {
508 new_targets->push_back(target);
513 std::unique_ptr<Uptane::Target> SotaUptaneClient::findTargetHelper(
const Uptane::Targets &cur_targets,
515 const int level,
const bool terminating,
516 const bool offline) {
518 const auto it = std::find_if(cur_targets.targets.cbegin(), cur_targets.targets.cend(), target_comp);
519 if (it != cur_targets.targets.cend()) {
520 return std_::make_unique<Uptane::Target>(*it);
523 if (terminating || level >= Uptane::kDelegationsMaxDepth) {
524 return std::unique_ptr<Uptane::Target>(
nullptr);
527 for (
const auto &delegate_name : cur_targets.delegated_role_names_) {
528 Uptane::Role delegate_role = Uptane::Role::Delegation(delegate_name);
529 auto patterns = cur_targets.paths_for_role_.find(delegate_role);
530 if (patterns == cur_targets.paths_for_role_.end()) {
535 for (
const auto &pattern : patterns->second) {
536 if (fnmatch(pattern.c_str(), queried_target.filename().c_str(), 0) == 0) {
548 Uptane::getTrustedDelegation(delegate_role, cur_targets, image_repo, *storage, *uptane_fetcher, offline);
549 if (delegation.isExpired(TimeStamp::Now())) {
553 auto is_terminating = cur_targets.terminating_role_.find(delegate_role);
554 if (is_terminating == cur_targets.terminating_role_.end()) {
558 auto found_target = findTargetHelper(delegation, queried_target, level + 1, is_terminating->second, offline);
559 if (found_target !=
nullptr) {
564 return std::unique_ptr<Uptane::Target>(
nullptr);
567 std::unique_ptr<Uptane::Target> SotaUptaneClient::findTargetInDelegationTree(
const Uptane::Target &target,
568 const bool offline) {
569 auto toplevel_targets = image_repo.getTargets();
570 if (toplevel_targets ==
nullptr) {
571 return std::unique_ptr<Uptane::Target>(
nullptr);
574 return findTargetHelper(*toplevel_targets, target, 0,
false, offline);
577 result::Download SotaUptaneClient::downloadImages(
const std::vector<Uptane::Target> &targets,
581 std::lock_guard<std::mutex> guard(download_mutex);
583 std::vector<Uptane::Target> downloaded_targets;
587 update_status = checkUpdatesOffline(targets);
588 }
catch (
const std::exception &e) {
589 last_exception = std::current_exception();
590 update_status = result::UpdateStatus::kError;
593 if (update_status == result::UpdateStatus::kNoUpdatesAvailable) {
595 }
else if (update_status == result::UpdateStatus::kError) {
596 result =
result::Download(downloaded_targets, result::DownloadStatus::kError,
"Error rechecking stored metadata.");
597 storeInstallationFailure(
601 if (update_status != result::UpdateStatus::kUpdatesAvailable) {
602 sendEvent<event::AllDownloadsComplete>(
result);
606 for (
const auto &target : targets) {
607 auto res = downloadImage(target, token);
609 downloaded_targets.push_back(res.second);
613 if (targets.size() == downloaded_targets.size()) {
616 if (downloaded_targets.size() == 0) {
617 LOG_ERROR <<
"0 of " << targets.size() <<
" targets were successfully downloaded.";
618 result =
result::Download(downloaded_targets, result::DownloadStatus::kError,
"Each target download has failed");
620 LOG_ERROR <<
"Only " << downloaded_targets.size() <<
" of " << targets.size() <<
" were successfully downloaded.";
623 storeInstallationFailure(
627 sendEvent<event::AllDownloadsComplete>(
result);
631 void SotaUptaneClient::reportPause() {
632 const std::string &correlation_id = director_repo.getCorrelationId();
633 report_queue->enqueue(std_::make_unique<DevicePausedReport>(correlation_id));
636 void SotaUptaneClient::reportResume() {
637 const std::string &correlation_id = director_repo.getCorrelationId();
638 report_queue->enqueue(std_::make_unique<DeviceResumedReport>(correlation_id));
641 std::pair<bool, Uptane::Target> SotaUptaneClient::downloadImage(
const Uptane::Target &target,
645 const std::string &correlation_id = director_repo.getCorrelationId();
647 for (
const auto &ecu : target.ecus()) {
648 report_queue->enqueue(std_::make_unique<EcuDownloadStartedReport>(ecu.first, correlation_id));
655 bool success =
false;
657 KeyManager keys(storage, config.keymanagerConfig());
659 auto prog_cb = [
this](
const Uptane::Target &t,
const std::string &description,
unsigned int progress) {
660 report_progress_cb(events_channel.get(), t, description, progress);
665 if (target.IsForEcu(primary_ecu_serial) || !target.
IsOstree()) {
667 const int max_tries = 3;
669 std::chrono::milliseconds wait(500);
671 for (; tries < max_tries; tries++) {
672 success = package_manager_->fetchTarget(target, *uptane_fetcher, keys, prog_cb, token);
675 if (success || (token !=
nullptr && !token->
canContinue(
false))) {
677 }
else if (tries < max_tries - 1) {
678 std::this_thread::sleep_for(wait);
683 LOG_ERROR <<
"Download unsuccessful after " << tries <<
" attempts.";
691 }
catch (
const std::exception &e) {
692 LOG_ERROR <<
"Error downloading image: " << e.what();
693 last_exception = std::current_exception();
698 for (
const auto &ecu : target.ecus()) {
699 report_queue->enqueue(std_::make_unique<EcuDownloadCompletedReport>(ecu.first, correlation_id, success));
702 sendEvent<event::DownloadTargetComplete>(target, success);
703 return {success, target};
706 void SotaUptaneClient::uptaneIteration(std::vector<Uptane::Target> *targets,
unsigned int *ecus_count) {
707 updateDirectorMeta();
709 std::vector<Uptane::Target> tmp_targets;
712 getNewTargets(&tmp_targets, &ecus);
713 }
catch (
const std::exception &e) {
714 LOG_ERROR <<
"Inconsistency between Director metadata and discovered ECUs";
718 if (tmp_targets.empty()) {
719 if (targets !=
nullptr) {
720 *targets = std::move(tmp_targets);
722 if (ecus_count !=
nullptr) {
728 LOG_INFO <<
"New updates found in Director metadata. Checking Image repo metadata...";
732 if (targets !=
nullptr) {
733 *targets = std::move(tmp_targets);
735 if (ecus_count !=
nullptr) {
740 void SotaUptaneClient::uptaneOfflineIteration(std::vector<Uptane::Target> *targets,
unsigned int *ecus_count) {
741 checkDirectorMetaOffline();
743 std::vector<Uptane::Target> tmp_targets;
746 getNewTargets(&tmp_targets, &ecus);
747 }
catch (
const std::exception &e) {
748 LOG_ERROR <<
"Inconsistency between Director metadata and existing ECUs: " << e.what();
752 if (tmp_targets.empty()) {
753 *targets = std::move(tmp_targets);
754 if (ecus_count !=
nullptr) {
760 checkImageMetaOffline();
762 *targets = std::move(tmp_targets);
763 if (ecus_count !=
nullptr) {
768 void SotaUptaneClient::sendDeviceData(
const Json::Value &custom_hwinfo) {
769 reportHwInfo(custom_hwinfo);
770 reportInstalledPackages();
772 reportAktualizrConfiguration();
774 sendEvent<event::SendDeviceDataComplete>();
782 if (hasPendingUpdates()) {
784 LOG_INFO <<
"The current update is pending. Check if pending ECUs has been already updated";
785 checkAndUpdatePendingSecondaries();
788 if (hasPendingUpdates()) {
791 LOG_INFO <<
"An update is pending. Skipping check for update until installation is complete.";
793 "There are pending updates, no new updates are checked");
797 if (!putManifestSimple()) {
798 LOG_ERROR <<
"Error sending manifest!";
801 sendEvent<event::UpdateCheckComplete>(
result);
809 std::vector<Uptane::Target> updates;
810 unsigned int ecus_count = 0;
812 uptaneIteration(&updates, &ecus_count);
813 }
catch (
const std::exception &e) {
814 last_exception = std::current_exception();
819 std::string director_targets;
820 if (!storage->loadNonRoot(&director_targets, Uptane::RepositoryType::Director(), Uptane::Role::Targets())) {
822 throw std::runtime_error(
"Could not get Director's Targets from storage");
825 if (updates.empty()) {
826 LOG_DEBUG <<
"No new updates found in Uptane metadata.";
828 result::UpdateCheck({}, 0, result::UpdateStatus::kNoUpdatesAvailable, Utils::parseJSON(director_targets),
"");
837 for (
auto &target : updates) {
838 auto image_target = findTargetInDelegationTree(target,
false);
839 if (image_target ==
nullptr) {
841 LOG_ERROR <<
"No matching target in Image repo Targets metadata for " << target;
846 if (target.uri().empty() && !image_target->uri().empty()) {
847 target.setUri(image_target->uri());
850 }
catch (
const std::exception &e) {
851 last_exception = std::current_exception();
854 storeInstallationFailure(
860 Utils::parseJSON(director_targets),
"");
861 if (updates.size() == 1) {
862 LOG_INFO <<
"1 new update found in both Director and Image repo metadata.";
864 LOG_INFO << updates.size() <<
" new updates found in both Director and Image repo metadata.";
869 result::UpdateStatus SotaUptaneClient::checkUpdatesOffline(
const std::vector<Uptane::Target> &targets) {
870 if (hasPendingUpdates()) {
872 LOG_INFO <<
"An update is pending. Skipping stored metadata check until installation is complete.";
873 return result::UpdateStatus::kError;
876 if (targets.empty()) {
877 LOG_WARNING <<
"Requested targets vector is empty. Nothing to do.";
878 return result::UpdateStatus::kError;
881 std::vector<Uptane::Target> director_targets;
882 unsigned int ecus_count = 0;
884 uptaneOfflineIteration(&director_targets, &ecus_count);
885 }
catch (
const std::exception &e) {
886 LOG_ERROR <<
"Invalid Uptane metadata in storage.";
890 if (director_targets.empty()) {
891 LOG_ERROR <<
"No new updates found in Uptane metadata, but expected " << targets.size() <<
".";
892 return result::UpdateStatus::kNoUpdatesAvailable;
898 for (
const auto &target : targets) {
900 const auto it = std::find_if(director_targets.cbegin(), director_targets.cend(), target_comp);
901 if (it == director_targets.cend()) {
902 LOG_ERROR <<
"No matching target in Director Targets metadata for " << target;
903 throw Uptane::Exception(Uptane::RepositoryType::DIRECTOR,
"No matching target in Director Targets metadata");
906 const auto image_target = findTargetInDelegationTree(target,
true);
907 if (image_target ==
nullptr) {
908 LOG_ERROR <<
"No matching target in Image repo Targets metadata for " << target;
909 throw Uptane::Exception(Uptane::RepositoryType::IMAGE,
"No matching target in Director Targets metadata");
913 return result::UpdateStatus::kUpdatesAvailable;
916 result::Install SotaUptaneClient::uptaneInstall(
const std::vector<Uptane::Target> &updates) {
917 const std::string &correlation_id = director_repo.getCorrelationId();
922 std::string raw_report;
924 std::tie(r, raw_report) = [
this, &updates, &correlation_id]() -> std::tuple<result::Install, std::string> {
931 update_status = checkUpdatesOffline(updates);
932 }
catch (
const std::exception &e) {
933 last_exception = std::current_exception();
934 update_status = result::UpdateStatus::kError;
937 if (update_status != result::UpdateStatus::kUpdatesAvailable) {
938 if (update_status == result::UpdateStatus::kNoUpdatesAvailable) {
943 return std::make_tuple(
result,
"Stored Uptane metadata is invalid");
948 for (
const auto &update : updates) {
949 if (update.IsForEcu(primary_ecu_serial) || !update.IsOstree()) {
955 if (package_manager_->verifyTarget(update) != TargetStatus::kGood) {
957 return std::make_tuple(
result,
"Downloaded target is invalid");
966 if (!waitSecondariesReachable(updates)) {
968 return std::make_tuple(
result,
"Secondaries were not available");
972 std::vector<Uptane::Target> primary_updates = findForEcu(updates, primary_ecu_serial);
975 if (!sendMetadataToEcus(updates)) {
977 return std::make_tuple(
result,
"Secondary metadata verification failed");
981 if (primary_updates.size() != 0u) {
984 primary_update.setCorrelationId(correlation_id);
986 report_queue->enqueue(std_::make_unique<EcuInstallationStartedReport>(primary_ecu_serial, correlation_id));
987 sendEvent<event::InstallStarted>(primary_ecu_serial);
990 if (!isInstalledOnPrimary(primary_update)) {
993 package_manager_->updateNotify();
994 install_res = PackageInstallSetResult(primary_update);
995 if (install_res.result_code.num_code == data::ResultCode::Numeric::kNeedCompletion) {
997 report_queue->enqueue(std_::make_unique<EcuInstallationAppliedReport>(primary_ecu_serial, correlation_id));
998 sendEvent<event::InstallTargetComplete>(primary_ecu_serial,
true);
999 }
else if (install_res.result_code.num_code == data::ResultCode::Numeric::kOk) {
1000 storage->saveEcuInstallationResult(primary_ecu_serial, install_res);
1001 report_queue->enqueue(
1002 std_::make_unique<EcuInstallationCompletedReport>(primary_ecu_serial, correlation_id,
true));
1003 sendEvent<event::InstallTargetComplete>(primary_ecu_serial,
true);
1006 storage->saveEcuInstallationResult(primary_ecu_serial, install_res);
1007 report_queue->enqueue(
1008 std_::make_unique<EcuInstallationCompletedReport>(primary_ecu_serial, correlation_id,
false));
1009 sendEvent<event::InstallTargetComplete>(primary_ecu_serial,
false);
1014 storage->saveEcuInstallationResult(primary_ecu_serial, install_res);
1016 report_queue->enqueue(
1017 std_::make_unique<EcuInstallationCompletedReport>(primary_ecu_serial, correlation_id,
false));
1018 sendEvent<event::InstallTargetComplete>(primary_ecu_serial,
false);
1020 result.ecu_reports.emplace(
result.ecu_reports.begin(), primary_update, primary_ecu_serial, install_res);
1022 LOG_INFO <<
"No update to install on Primary";
1025 auto sec_reports = sendImagesToEcus(updates);
1026 result.ecu_reports.insert(
result.ecu_reports.end(), sec_reports.begin(), sec_reports.end());
1028 computeDeviceInstallationResult(&
result.dev_report, &rr);
1030 return std::make_tuple(
result, rr);
1034 storage->storeDeviceInstallationResult(r.dev_report, raw_report, correlation_id);
1036 sendEvent<event::AllInstallsComplete>(r);
1042 auto campaigns = campaign::Campaign::fetchAvailableCampaigns(*http, config.tls.server);
1043 for (
const auto &c : campaigns) {
1044 LOG_INFO <<
"Campaign: " << c.name;
1045 LOG_INFO <<
"Campaign id: " << c.id;
1046 LOG_INFO <<
"Campaign size: " << c.size;
1047 LOG_INFO <<
"CampaignAccept required: " << (c.autoAccept ?
"no" :
"yes");
1048 LOG_INFO <<
"Message: " << c.description;
1051 sendEvent<event::CampaignCheckComplete>(
result);
1055 void SotaUptaneClient::campaignAccept(
const std::string &campaign_id) {
1056 sendEvent<event::CampaignAcceptComplete>();
1057 report_queue->enqueue(std_::make_unique<CampaignAcceptedReport>(campaign_id));
1060 void SotaUptaneClient::campaignDecline(
const std::string &campaign_id) {
1061 sendEvent<event::CampaignDeclineComplete>();
1062 report_queue->enqueue(std_::make_unique<CampaignDeclinedReport>(campaign_id));
1065 void SotaUptaneClient::campaignPostpone(
const std::string &campaign_id) {
1066 sendEvent<event::CampaignPostponeComplete>();
1067 report_queue->enqueue(std_::make_unique<CampaignPostponedReport>(campaign_id));
1070 bool SotaUptaneClient::isInstallCompletionRequired()
const {
1071 std::vector<std::pair<Uptane::EcuSerial, Hash>> pending_ecus;
1072 storage->getPendingEcus(&pending_ecus);
1073 bool pending_for_ecu = std::find_if(pending_ecus.begin(), pending_ecus.end(),
1074 [
this](
const std::pair<Uptane::EcuSerial, Hash> &ecu) ->
bool {
1075 return ecu.first == primary_ecu_serial_;
1076 }) != pending_ecus.end();
1078 return pending_for_ecu && config.uptane.force_install_completion;
1081 void SotaUptaneClient::completeInstall()
const {
1082 if (isInstallCompletionRequired()) {
1083 package_manager_->completeInstall();
1087 bool SotaUptaneClient::putManifestSimple(
const Json::Value &custom) {
1089 if (hasPendingUpdates()) {
1092 LOG_DEBUG <<
"An update is pending. Skipping manifest upload until installation is complete.";
1096 static bool connected =
true;
1097 auto manifest = AssembleManifest();
1098 if (custom != Json::nullValue) {
1099 manifest[
"custom"] = custom;
1101 auto signed_manifest = uptane_manifest->sign(manifest);
1102 HttpResponse response = http->put(config.uptane.director_server +
"/manifest", signed_manifest);
1103 if (response.isOk()) {
1105 LOG_INFO <<
"Connectivity is restored.";
1108 storage->clearInstallationResults();
1115 LOG_WARNING <<
"Put manifest request failed: " << response.getStatusStr();
1119 bool SotaUptaneClient::putManifest(
const Json::Value &custom) {
1120 bool success = putManifestSimple(custom);
1121 sendEvent<event::PutManifestComplete>(success);
1126 void SotaUptaneClient::verifySecondaries() {
1127 storage->clearMisconfiguredEcus();
1129 if (!storage->loadEcuSerials(&serials) || serials.empty()) {
1130 LOG_ERROR <<
"No ECU serials found in storage!";
1134 std::vector<MisconfiguredEcu> misconfigured_ecus;
1135 std::vector<bool> found(serials.size(),
false);
1137 EcuSerials::const_iterator store_it;
1138 store_it = std::find_if(serials.cbegin(), serials.cend(), primary_comp);
1139 if (store_it == serials.cend()) {
1140 LOG_ERROR <<
"Primary ECU serial " << primaryEcuSerial() <<
" not found in storage!";
1143 found[static_cast<size_t>(std::distance(serials.cbegin(), store_it))] =
true;
1146 for (
auto it = secondaries.cbegin(); it != secondaries.cend(); ++it) {
1148 store_it = std::find_if(serials.cbegin(), serials.cend(), secondary_comp);
1149 if (store_it == serials.cend()) {
1150 LOG_ERROR <<
"Secondary ECU serial " << it->second->getSerial() <<
" (hardware ID " << it->second->getHwId()
1151 <<
") not found in storage!";
1152 misconfigured_ecus.emplace_back(it->second->getSerial(), it->second->getHwId(), EcuState::kNotRegistered);
1153 }
else if (found[static_cast<size_t>(std::distance(serials.cbegin(), store_it))]) {
1154 LOG_ERROR <<
"Secondary ECU serial " << it->second->getSerial() <<
" (hardware ID " << it->second->getHwId()
1155 <<
") has a duplicate entry in storage!";
1157 found[static_cast<size_t>(std::distance(serials.cbegin(), store_it))] =
true;
1161 std::vector<bool>::iterator found_it;
1162 for (found_it = found.begin(); found_it != found.end(); ++found_it) {
1164 auto not_registered = serials[static_cast<size_t>(std::distance(found.begin(), found_it))];
1165 LOG_WARNING <<
"ECU serial " << not_registered.first <<
" in storage was not reported to aktualizr!";
1166 misconfigured_ecus.emplace_back(not_registered.first, not_registered.second, EcuState::kOld);
1170 storage->storeMisconfiguredEcus(misconfigured_ecus);
1173 bool SotaUptaneClient::waitSecondariesReachable(
const std::vector<Uptane::Target> &updates) {
1174 std::map<Uptane::EcuSerial, Uptane::SecondaryInterface *> targeted_secondaries;
1176 for (
const auto &t : updates) {
1177 for (
const auto &ecu : t.ecus()) {
1178 if (ecu.first == primary_ecu_serial) {
1181 auto f = secondaries.find(ecu.first);
1182 if (f == secondaries.end()) {
1183 LOG_ERROR <<
"Target " << t <<
" has unknown ECU ID";
1187 targeted_secondaries[ecu.first] = f->second.get();
1191 if (targeted_secondaries.empty()) {
1195 LOG_INFO <<
"Waiting for Secondaries to connect to start installation...";
1197 auto deadline = std::chrono::system_clock::now() + std::chrono::seconds(config.uptane.secondary_preinstall_wait_sec);
1198 while (std::chrono::system_clock::now() <= deadline) {
1199 if (targeted_secondaries.empty()) {
1203 for (
auto sec_it = targeted_secondaries.begin(); sec_it != targeted_secondaries.end();) {
1204 if (sec_it->second->ping()) {
1205 sec_it = targeted_secondaries.erase(sec_it);
1210 std::this_thread::sleep_for(std::chrono::seconds(1));
1213 LOG_ERROR <<
"Secondaries did not connect: ";
1215 for (
const auto &sec : targeted_secondaries) {
1216 LOG_ERROR << sec.second->getSerial();
1225 const std::string &correlation_id = director_repo.getCorrelationId();
1226 storage->storeDeviceInstallationResult(
result,
"", correlation_id);
1228 director_repo.dropTargets(*storage);
1232 std::string latest_root;
1234 if (!storage->loadLatestRoot(&latest_root, repo)) {
1235 LOG_ERROR <<
"No Root metadata to send";
1239 int last_root_version = Uptane::extractVersionUntrusted(latest_root);
1241 int sec_root_version = secondary.getRootVersion((repo == Uptane::RepositoryType::Director()));
1242 if (sec_root_version >= 0) {
1243 for (
int v = sec_root_version + 1; v <= last_root_version; v++) {
1246 LOG_WARNING <<
"Couldn't find Root metadata in the storage, trying remote repo";
1248 uptane_fetcher->fetchRole(&root, Uptane::kMaxRootSize, repo, Uptane::Role::Root(),
Uptane::Version(v));
1249 }
catch (
const std::exception &e) {
1251 LOG_ERROR <<
"Root metadata could not be fetched, skipping to the next Secondary";
1255 if (!secondary.putRoot(root, repo == Uptane::RepositoryType::Director())) {
1256 LOG_ERROR <<
"Sending metadata to " << secondary.getSerial() <<
" failed";
1263 bool SotaUptaneClient::sendMetadataToEcus(
const std::vector<Uptane::Target> &targets) {
1265 if (!storage->loadLatestRoot(&meta.director_root, Uptane::RepositoryType::Director())) {
1266 LOG_ERROR <<
"No Director Root metadata to send";
1269 if (!storage->loadNonRoot(&meta.director_targets, Uptane::RepositoryType::Director(), Uptane::Role::Targets())) {
1270 LOG_ERROR <<
"No Director Targets metadata to send";
1273 if (!storage->loadLatestRoot(&meta.image_root, Uptane::RepositoryType::Image())) {
1274 LOG_ERROR <<
"No Image repo Root metadata to send";
1277 if (!storage->loadNonRoot(&meta.image_timestamp, Uptane::RepositoryType::Image(), Uptane::Role::Timestamp())) {
1278 LOG_ERROR <<
"No Image repo Timestamp metadata to send";
1281 if (!storage->loadNonRoot(&meta.image_snapshot, Uptane::RepositoryType::Image(), Uptane::Role::Snapshot())) {
1282 LOG_ERROR <<
"No Image repo Snapshot metadata to send";
1285 if (!storage->loadNonRoot(&meta.image_targets, Uptane::RepositoryType::Image(), Uptane::Role::Targets())) {
1286 LOG_ERROR <<
"No Image repo Targets metadata to send";
1290 bool put_meta_succeed =
true;
1291 for (
const auto &target : targets) {
1292 for (
const auto &ecu : target.ecus()) {
1294 auto sec = secondaries.find(ecu_serial);
1295 if (sec == secondaries.end()) {
1300 rotateSecondaryRoot(Uptane::RepositoryType::Director(), *(sec->second));
1301 rotateSecondaryRoot(Uptane::RepositoryType::Image(), *(sec->second));
1302 if (!sec->second->putMetadata(meta)) {
1303 LOG_ERROR <<
"Sending metadata to " << sec->first <<
" failed";
1304 put_meta_succeed =
false;
1309 return put_meta_succeed;
1314 auto f = [
this, &secondary, target]() {
1315 const std::string &correlation_id = director_repo.getCorrelationId();
1317 sendEvent<event::InstallStarted>(secondary.getSerial());
1318 report_queue->enqueue(std_::make_unique<EcuInstallationStartedReport>(secondary.getSerial(), correlation_id));
1320 std::string data_to_send;
1321 bool send_firmware_result =
false;
1325 data_to_send = secondaryTreehubCredentials();
1327 std::stringstream sstr;
1328 sstr << *storage->openTargetFile(target);
1329 data_to_send = sstr.str();
1332 if (!data_to_send.empty()) {
1333 send_firmware_result = secondary.sendFirmware(data_to_send);
1339 if (send_firmware_result) {
1340 result = secondary.install(target.filename());
1343 if (
result == data::ResultCode::Numeric::kNeedCompletion) {
1344 report_queue->enqueue(std_::make_unique<EcuInstallationAppliedReport>(secondary.getSerial(), correlation_id));
1346 report_queue->enqueue(std_::make_unique<EcuInstallationCompletedReport>(
1347 secondary.getSerial(), correlation_id,
result == data::ResultCode::Numeric::kOk));
1350 sendEvent<event::InstallTargetComplete>(secondary.getSerial(),
result == data::ResultCode::Numeric::kOk);
1354 return std::async(std::launch::async, f);
1357 std::vector<result::Install::EcuReport> SotaUptaneClient::sendImagesToEcus(
const std::vector<Uptane::Target> &targets) {
1358 std::vector<result::Install::EcuReport> reports;
1359 std::vector<std::pair<result::Install::EcuReport, std::future<data::ResultCode::Numeric>>> firmwareFutures;
1363 for (
auto targets_it = targets.cbegin(); targets_it != targets.cend(); ++targets_it) {
1364 for (
auto ecus_it = targets_it->ecus().cbegin(); ecus_it != targets_it->ecus().cend(); ++ecus_it) {
1367 if (primary_ecu_serial == ecu_serial) {
1371 auto f = secondaries.find(ecu_serial);
1372 if (f == secondaries.end()) {
1373 LOG_ERROR <<
"Target " << *targets_it <<
" has unknown ECU ID";
1379 sendFirmwareAsync(sec, *targets_it));
1383 for (
auto &f : firmwareFutures) {
1386 fut_result = f.second.get();
1389 if (fiu_fail((std::string(
"secondary_install_") + f.first.serial.ToString()).c_str()) != 0) {
1402 if (fut_result == data::ResultCode::Numeric::kOk || fut_result == data::ResultCode::Numeric::kNeedCompletion) {
1403 f.first.update.setCorrelationId(director_repo.getCorrelationId());
1404 auto update_mode = fut_result == data::ResultCode::Numeric::kOk ? InstalledVersionUpdateMode::kCurrent
1405 : InstalledVersionUpdateMode::kPending;
1406 storage->saveInstalledVersion(f.first.serial.ToString(), f.first.update, update_mode);
1409 storage->saveEcuInstallationResult(f.first.serial, f.first.install_res);
1410 reports.push_back(f.first);
1415 std::string SotaUptaneClient::secondaryTreehubCredentials()
const {
1416 if (config.tls.pkey_source != CryptoSource::kFile || config.tls.cert_source != CryptoSource::kFile ||
1417 config.tls.ca_source != CryptoSource::kFile) {
1418 LOG_ERROR <<
"Cannot send OSTree update to a Secondary when not using file as credential sources";
1421 std::string ca, cert, pkey;
1422 if (!storage->loadTlsCreds(&ca, &cert, &pkey)) {
1423 LOG_ERROR <<
"Could not load TLS credentials from storage";
1427 std::string treehub_url = config.pacman.ostree_server;
1428 std::map<std::string, std::string> archive_map = {
1429 {
"ca.pem", ca}, {
"client.pem", cert}, {
"pkey.pem", pkey}, {
"server.url", treehub_url}};
1432 std::stringstream as;
1433 Utils::writeArchive(archive_map, as);
1436 }
catch (std::runtime_error &exc) {
1437 LOG_ERROR <<
"Could not create credentials archive: " << exc.what();
1446 void SotaUptaneClient::checkAndUpdatePendingSecondaries() {
1448 std::vector<std::pair<Uptane::EcuSerial, Hash>> pending_ecus;
1449 storage->getPendingEcus(&pending_ecus);
1451 for (
const auto &pending_ecu : pending_ecus) {
1452 if (primaryEcuSerial() == pending_ecu.first) {
1455 auto &sec = secondaries[pending_ecu.first];
1456 const auto &manifest = sec->getManifest();
1457 if (manifest == Json::nullValue) {
1458 LOG_DEBUG <<
"Failed to get a manifest from Secondary: " << sec->getSerial();
1461 if (!manifest.verifySignature(sec->getPublicKey())) {
1462 LOG_ERROR <<
"Invalid signature of the manifest reported by Secondary: "
1463 <<
" serial: " << pending_ecu.first <<
" manifest: " << manifest;
1467 auto current_ecu_hash = manifest.installedImageHash();
1468 if (pending_ecu.second == current_ecu_hash) {
1469 LOG_INFO <<
"The pending update " << current_ecu_hash <<
" has been installed on " << pending_ecu.first;
1470 boost::optional<Uptane::Target> pending_version;
1471 if (storage->loadInstalledVersions(pending_ecu.first.ToString(),
nullptr, &pending_version)) {
1472 storage->saveEcuInstallationResult(pending_ecu.first,
1475 storage->saveInstalledVersion(pending_ecu.first.ToString(), *pending_version,
1476 InstalledVersionUpdateMode::kCurrent);
1478 report_queue->enqueue(std_::make_unique<EcuInstallationCompletedReport>(
1479 pending_ecu.first, pending_version->correlation_id(),
true));
1482 std::string raw_report;
1483 computeDeviceInstallationResult(&ir, &raw_report);
1484 storage->storeDeviceInstallationResult(ir, raw_report, pending_version->correlation_id());
1490 boost::optional<Uptane::HardwareIdentifier> SotaUptaneClient::ecuHwId(
const Uptane::EcuSerial &serial)
const {
1491 if (serial == primary_ecu_serial_ || serial.ToString().empty()) {
1492 if (primary_ecu_hw_id_ == Uptane::HardwareIdentifier::Unknown()) {
1495 return primary_ecu_hw_id_;
1498 const auto it = secondaries.find(serial);
1499 if (it != secondaries.end()) {
1500 return it->second->getHwId();