1 #include "sotauptaneclient.h"
8 #include "crypto/crypto.h"
9 #include "crypto/keymanager.h"
10 #include "initializer.h"
11 #include "libaktualizr/campaign.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<SecondaryInterface> &sec) {
30 const auto map_it = secondaries.find(serial);
31 if (map_it != secondaries.end()) {
32 throw std::runtime_error(std::string(
"Multiple Secondaries found with the same serial: ") + serial.ToString());
35 secondaries.emplace(serial, sec);
38 std::vector<Uptane::Target> SotaUptaneClient::findForEcu(
const std::vector<Uptane::Target> &targets,
40 std::vector<Uptane::Target>
result;
41 for (
auto it = targets.begin(); it != targets.end(); ++it) {
42 if (it->ecus().find(ecu_id) != it->ecus().end()) {
50 LOG_INFO <<
"Installing package using " << package_manager_->name() <<
" package manager";
52 return package_manager_->install(target);
53 }
catch (std::exception &ex) {
54 LOG_ERROR <<
"Installation failed: " << ex.what();
59 void SotaUptaneClient::finalizeAfterReboot() {
62 if (!hasPendingUpdates()) {
63 LOG_DEBUG <<
"No pending updates, continuing with initialization";
67 LOG_INFO <<
"Checking for a pending update to apply for Primary ECU";
70 boost::optional<Uptane::Target> pending_target;
71 storage->loadInstalledVersions(primary_ecu_serial.ToString(),
nullptr, &pending_target);
73 if (!pending_target) {
74 LOG_ERROR <<
"No pending update for Primary ECU found, continuing with initialization";
78 LOG_INFO <<
"Pending update for Primary ECU was found, trying to apply it...";
82 if (install_res.result_code == data::ResultCode::Numeric::kNeedCompletion) {
83 LOG_INFO <<
"Pending update for Primary ECU was not applied because reboot was not detected, "
84 "continuing with initialization";
88 storage->saveEcuInstallationResult(primary_ecu_serial, install_res);
90 const std::string correlation_id = pending_target->correlation_id();
91 if (install_res.success) {
92 storage->saveInstalledVersion(primary_ecu_serial.ToString(), *pending_target, InstalledVersionUpdateMode::kCurrent);
94 report_queue->enqueue(std_::make_unique<EcuInstallationCompletedReport>(primary_ecu_serial, correlation_id,
true));
97 storage->saveInstalledVersion(primary_ecu_serial.ToString(), *pending_target, InstalledVersionUpdateMode::kNone);
98 report_queue->enqueue(std_::make_unique<EcuInstallationCompletedReport>(primary_ecu_serial, correlation_id,
false));
101 director_repo.dropTargets(*storage);
104 std::string raw_report;
105 computeDeviceInstallationResult(&ir, &raw_report);
106 storage->storeDeviceInstallationResult(ir, raw_report, correlation_id);
122 storage->saveInstalledVersion(ecu_serial.ToString(), target, InstalledVersionUpdateMode::kNone);
124 result = PackageInstall(target);
125 if (
result.result_code.num_code == data::ResultCode::Numeric::kOk) {
127 storage->saveInstalledVersion(ecu_serial.ToString(), target, InstalledVersionUpdateMode::kCurrent);
128 }
else if (
result.result_code.num_code == data::ResultCode::Numeric::kNeedCompletion) {
130 storage->saveInstalledVersion(ecu_serial.ToString(), target, InstalledVersionUpdateMode::kPending);
132 storage->saveEcuInstallationResult(ecu_serial,
result);
141 void SotaUptaneClient::reportHwInfo(
const Json::Value &custom_hwinfo) {
142 Json::Value system_info;
143 std::string stored_hash;
144 storage->loadDeviceDataHash(
"hardware_info", &stored_hash);
146 if (custom_hwinfo.empty()) {
147 if (!stored_hash.empty()) {
148 LOG_TRACE <<
"Not reporting default hardware information because it has already been reported";
151 system_info = Utils::getHardwareInfo();
152 if (system_info.empty()) {
153 LOG_WARNING <<
"Unable to fetch hardware information from host system.";
158 const Json::Value &hw_info = custom_hwinfo.empty() ? system_info : custom_hwinfo;
159 const Hash new_hash = Hash::generate(Hash::Type::kSha256, Utils::jsonToCanonicalStr(hw_info));
160 if (new_hash !=
Hash(Hash::Type::kSha256, stored_hash)) {
161 if (custom_hwinfo.empty()) {
162 LOG_DEBUG <<
"Reporting default hardware information";
164 LOG_DEBUG <<
"Reporting custom hardware information";
166 const HttpResponse response = http->put(config.tls.server +
"/system_info", hw_info);
167 if (response.isOk()) {
168 storage->storeDeviceDataHash(
"hardware_info", new_hash.HashString());
171 LOG_TRACE <<
"Not reporting hardware information because it has not changed";
175 void SotaUptaneClient::reportInstalledPackages() {
176 const Json::Value packages = package_manager_->getInstalledPackages();
177 const Hash new_hash = Hash::generate(Hash::Type::kSha256, Utils::jsonToCanonicalStr(packages));
178 std::string stored_hash;
179 if (!(storage->loadDeviceDataHash(
"installed_packages", &stored_hash) &&
180 new_hash ==
Hash(Hash::Type::kSha256, stored_hash))) {
181 LOG_DEBUG <<
"Reporting installed packages";
182 const HttpResponse response = http->put(config.tls.server +
"/core/installed", packages);
183 if (response.isOk()) {
184 storage->storeDeviceDataHash(
"installed_packages", new_hash.HashString());
187 LOG_TRACE <<
"Not reporting installed packages because they have not changed";
191 void SotaUptaneClient::reportNetworkInfo() {
192 if (!config.telemetry.report_network) {
193 LOG_TRACE <<
"Not reporting network information because telemetry is disabled";
197 Json::Value network_info;
199 network_info = Utils::getNetworkInfo();
200 }
catch (
const std::exception &ex) {
201 LOG_ERROR <<
"Failed to get network info: " << ex.what();
204 const Hash new_hash = Hash::generate(Hash::Type::kSha256, Utils::jsonToCanonicalStr(network_info));
205 std::string stored_hash;
206 if (!(storage->loadDeviceDataHash(
"network_info", &stored_hash) &&
207 new_hash ==
Hash(Hash::Type::kSha256, stored_hash))) {
208 LOG_DEBUG <<
"Reporting network information";
209 const HttpResponse response = http->put(config.tls.server +
"/system_info/network", network_info);
210 if (response.isOk()) {
211 storage->storeDeviceDataHash(
"network_info", new_hash.HashString());
214 LOG_TRACE <<
"Not reporting network information because it has not changed";
218 void SotaUptaneClient::reportAktualizrConfiguration() {
219 if (!config.telemetry.report_config) {
220 LOG_TRACE <<
"Not reporting libaktualizr configuration because telemetry is disabled";
224 std::stringstream conf_ss;
225 config.writeToStream(conf_ss);
226 const std::string conf_str = conf_ss.str();
227 const Hash new_hash = Hash::generate(Hash::Type::kSha256, conf_str);
228 std::string stored_hash;
229 if (!(storage->loadDeviceDataHash(
"configuration", &stored_hash) &&
230 new_hash ==
Hash(Hash::Type::kSha256, stored_hash))) {
231 LOG_DEBUG <<
"Reporting libaktualizr configuration";
232 const HttpResponse response = http->post(config.tls.server +
"/system_info/config",
"application/toml", conf_str);
233 if (response.isOk()) {
234 storage->storeDeviceDataHash(
"configuration", new_hash.HashString());
237 LOG_TRACE <<
"Not reporting libaktualizr configuration because it has not changed";
241 Json::Value SotaUptaneClient::AssembleManifest() {
242 Json::Value manifest;
244 manifest[
"primary_ecu_serial"] = primary_ecu_serial.ToString();
247 Json::Value version_manifest;
249 Json::Value primary_manifest = uptane_manifest->assembleManifest(package_manager_->getCurrent());
250 std::vector<std::pair<Uptane::EcuSerial, int64_t>> ecu_cnt;
251 std::string report_counter;
252 if (!storage->loadEcuReportCounter(&ecu_cnt) || ecu_cnt.empty()) {
253 LOG_ERROR <<
"No ECU version report counter, please check the database!";
256 report_counter = std::to_string(ecu_cnt[0].second + 1);
257 storage->saveEcuReportCounter(ecu_cnt[0].first, ecu_cnt[0].second + 1);
259 version_manifest[primary_ecu_serial.ToString()] = uptane_manifest->sign(primary_manifest, report_counter);
261 for (
auto it = secondaries.begin(); it != secondaries.end(); it++) {
265 secmanifest = it->second->getManifest();
266 }
catch (
const std::exception &ex) {
268 LOG_DEBUG <<
"Failed to get manifest from Secondary with serial " << ecu_serial <<
": " << ex.what();
271 bool from_cache =
false;
272 if (secmanifest.empty()) {
275 if (storage->loadCachedEcuManifest(ecu_serial, &cached)) {
276 LOG_WARNING <<
"Could not reach Secondary " << ecu_serial <<
", sending a cached version of its manifest";
277 secmanifest = Utils::parseJSON(cached);
280 LOG_ERROR <<
"Failed to get a valid manifest from Secondary with serial " << ecu_serial <<
" or from cache!";
285 bool verified =
false;
287 verified = secmanifest.verifySignature(it->second->getPublicKey());
288 }
catch (
const std::exception &ex) {
289 LOG_ERROR <<
"Failed to get public key from Secondary with serial " << ecu_serial <<
": " << ex.what();
292 version_manifest[ecu_serial.ToString()] = secmanifest;
294 storage->storeCachedEcuManifest(ecu_serial, Utils::jsonToCanonicalStr(secmanifest));
298 LOG_ERROR <<
"Invalid manifest or signature reported by Secondary: "
299 <<
" serial: " << ecu_serial <<
" manifest: " << secmanifest;
302 manifest[
"ecu_version_manifests"] = version_manifest;
305 Json::Value installation_report;
308 std::string raw_report;
309 std::string correlation_id;
310 bool has_results = storage->loadDeviceInstallationResult(&dev_result, &raw_report, &correlation_id);
312 if (!(dev_result.isSuccess() || dev_result.needCompletion())) {
313 director_repo.dropTargets(*storage);
316 installation_report[
"result"] = dev_result.toJson();
317 installation_report[
"raw_report"] = raw_report;
318 installation_report[
"correlation_id"] = correlation_id;
319 installation_report[
"items"] = Json::arrayValue;
321 std::vector<std::pair<Uptane::EcuSerial, data::InstallationResult>> ecu_results;
322 storage->loadEcuInstallationResults(&ecu_results);
323 for (
const auto &r : ecu_results) {
328 item[
"ecu"] = serial.ToString();
329 item[
"result"] = res.toJson();
331 installation_report[
"items"].append(item);
334 manifest[
"installation_report"][
"content_type"] =
"application/vnd.com.here.otac.installationReport.v1";
335 manifest[
"installation_report"][
"report"] = installation_report;
337 LOG_DEBUG <<
"No installation result to report in manifest";
343 bool SotaUptaneClient::hasPendingUpdates()
const {
return storage->hasPendingInstall(); }
345 void SotaUptaneClient::initialize() {
346 LOG_DEBUG <<
"Checking if device is provisioned...";
347 auto keys = std::make_shared<KeyManager>(storage, config.keymanagerConfig());
349 Initializer initializer(config.provision, storage, http, *keys, secondaries);
353 if (!storage->loadEcuSerials(&serials) || serials.empty()) {
354 throw std::runtime_error(
"Unable to load ECU serials after device registration.");
357 uptane_manifest = std::make_shared<Uptane::ManifestIssuer>(keys, serials[0].first);
358 primary_ecu_serial_ = serials[0].first;
359 primary_ecu_hw_id_ = serials[0].second;
360 LOG_INFO <<
"Primary ECU serial: " << primary_ecu_serial_ <<
" with hardware ID: " << primary_ecu_hw_id_;
362 for (
auto it = secondaries.begin(); it != secondaries.end(); ++it) {
364 it->second->init(secondary_provider_);
365 }
catch (
const std::exception &ex) {
366 LOG_ERROR <<
"Failed to initialize Secondary with serial " << it->first <<
": " << ex.what();
370 std::string device_id;
371 if (!storage->loadDeviceId(&device_id)) {
372 throw std::runtime_error(
"Unable to load device ID after device registration.");
374 LOG_INFO <<
"Device ID: " << device_id;
375 LOG_INFO <<
"Device Gateway URL: " << config.tls.server;
379 std::string not_before;
380 std::string not_after;
381 keys->getCertInfo(&subject, &issuer, ¬_before, ¬_after);
382 LOG_INFO <<
"Certificate subject: " << subject;
383 LOG_INFO <<
"Certificate issuer: " << issuer;
384 LOG_INFO <<
"Certificate valid from: " << not_before <<
" until: " << not_after;
386 LOG_DEBUG <<
"... provisioned OK";
387 finalizeAfterReboot();
390 void SotaUptaneClient::updateDirectorMeta() {
392 director_repo.updateMeta(*storage, *uptane_fetcher);
393 }
catch (
const std::exception &e) {
394 LOG_ERROR <<
"Director metadata update failed: " << e.what();
399 void SotaUptaneClient::updateImageMeta() {
401 image_repo.updateMeta(*storage, *uptane_fetcher);
402 }
catch (
const std::exception &e) {
403 LOG_ERROR <<
"Failed to update Image repo metadata: " << e.what();
408 void SotaUptaneClient::checkDirectorMetaOffline() {
410 director_repo.checkMetaOffline(*storage);
411 }
catch (
const std::exception &e) {
412 LOG_ERROR <<
"Failed to check Director metadata: " << e.what();
417 void SotaUptaneClient::checkImageMetaOffline() {
419 image_repo.checkMetaOffline(*storage);
420 }
catch (
const std::exception &e) {
421 LOG_ERROR <<
"Failed to check Image repo metadata: " << e.what();
426 std::string *raw_installation_report)
const {
429 std::string raw_ir =
"Installation succesful";
432 std::vector<std::pair<Uptane::EcuSerial, data::InstallationResult>> ecu_results;
434 if (!storage->loadEcuInstallationResults(&ecu_results)) {
437 "Unable to get installation results from ECUs");
438 raw_ir =
"Failed to load ECU installation results";
442 std::string result_code_err_str;
444 for (
const auto &r : ecu_results) {
445 auto ecu_serial = r.first;
446 auto installation_res = r.second;
448 auto hw_id = getEcuHwId(ecu_serial);
453 "Unable to get installation results from ECUs");
455 raw_ir =
"Failed to find an ECU with the given serial: " + ecu_serial.ToString();
459 if (installation_res.needCompletion()) {
461 device_installation_result =
463 "ECU needs completion/finalization to be installed: " + ecu_serial.ToString());
464 raw_ir =
"ECU needs completion/finalization to be installed: " + ecu_serial.ToString();
470 if (!installation_res.isSuccess()) {
471 const std::string ecu_code_str = (*hw_id).ToString() +
":" + installation_res.result_code.toString();
472 result_code_err_str += (!result_code_err_str.empty() ?
"|" :
"") + ecu_code_str;
476 if (!result_code_err_str.empty()) {
478 device_installation_result =
480 "Installation failed on one or more ECUs");
481 raw_ir =
"Installation failed on one or more ECUs";
489 *
result = device_installation_result;
492 if (raw_installation_report !=
nullptr) {
493 *raw_installation_report = raw_ir;
497 void SotaUptaneClient::getNewTargets(std::vector<Uptane::Target> *new_targets,
unsigned int *ecus_count) {
498 const std::vector<Uptane::Target> targets = director_repo.getTargets().targets;
500 if (ecus_count !=
nullptr) {
505 for (
const auto &ecu : target.ecus()) {
513 const auto hw_id_known = getEcuHwId(ecu_serial);
518 LOG_ERROR <<
"Unknown ECU ID in Director Targets metadata: " << ecu_serial.ToString();
522 if (*hw_id_known != hw_id) {
523 LOG_ERROR <<
"Wrong hardware identifier for ECU " << ecu_serial.ToString();
527 boost::optional<Uptane::Target> current_version;
528 if (!storage->loadInstalledVersions(ecu_serial.ToString(), ¤t_version,
nullptr)) {
529 LOG_WARNING <<
"Could not load currently installed version for ECU ID: " << ecu_serial.ToString();
533 if (!current_version) {
534 LOG_WARNING <<
"Current version for ECU ID: " << ecu_serial.ToString() <<
" is unknown";
536 }
else if (current_version->MatchTarget(target)) {
538 }
else if (current_version->filename() == target.filename()) {
539 LOG_ERROR <<
"Director Target filename matches currently installed version, but content differs!";
548 if (primary_ecu_serial == ecu_serial) {
550 (config.pacman.type == PACKAGE_MANAGER_OSTREE || config.pacman.type == PACKAGE_MANAGER_OSTREEDOCKERAPP)) {
551 LOG_ERROR <<
"Cannot install a non-OSTree package on an OSTree system";
556 if (is_new && ecus_count !=
nullptr) {
562 new_targets->push_back(target);
567 std::unique_ptr<Uptane::Target> SotaUptaneClient::findTargetHelper(
const Uptane::Targets &cur_targets,
569 const int level,
const bool terminating,
570 const bool offline) {
572 const auto it = std::find_if(cur_targets.targets.cbegin(), cur_targets.targets.cend(), target_comp);
573 if (it != cur_targets.targets.cend()) {
574 return std_::make_unique<Uptane::Target>(*it);
577 if (terminating || level >= Uptane::kDelegationsMaxDepth) {
578 return std::unique_ptr<Uptane::Target>(
nullptr);
581 for (
const auto &delegate_name : cur_targets.delegated_role_names_) {
582 Uptane::Role delegate_role = Uptane::Role::Delegation(delegate_name);
583 auto patterns = cur_targets.paths_for_role_.find(delegate_role);
584 if (patterns == cur_targets.paths_for_role_.end()) {
589 for (
const auto &pattern : patterns->second) {
590 if (fnmatch(pattern.c_str(), queried_target.filename().c_str(), 0) == 0) {
602 Uptane::getTrustedDelegation(delegate_role, cur_targets, image_repo, *storage, *uptane_fetcher, offline);
603 if (delegation.isExpired(TimeStamp::Now())) {
607 auto is_terminating = cur_targets.terminating_role_.find(delegate_role);
608 if (is_terminating == cur_targets.terminating_role_.end()) {
612 auto found_target = findTargetHelper(delegation, queried_target, level + 1, is_terminating->second, offline);
613 if (found_target !=
nullptr) {
618 return std::unique_ptr<Uptane::Target>(
nullptr);
621 std::unique_ptr<Uptane::Target> SotaUptaneClient::findTargetInDelegationTree(
const Uptane::Target &target,
622 const bool offline) {
623 auto toplevel_targets = image_repo.getTargets();
624 if (toplevel_targets ==
nullptr) {
625 return std::unique_ptr<Uptane::Target>(
nullptr);
628 return findTargetHelper(*toplevel_targets, target, 0,
false, offline);
631 result::Download SotaUptaneClient::downloadImages(
const std::vector<Uptane::Target> &targets,
635 std::lock_guard<std::mutex> guard(download_mutex);
637 std::vector<Uptane::Target> downloaded_targets;
641 update_status = checkUpdatesOffline(targets);
642 }
catch (
const std::exception &e) {
643 last_exception = std::current_exception();
644 update_status = result::UpdateStatus::kError;
647 if (update_status == result::UpdateStatus::kNoUpdatesAvailable) {
649 }
else if (update_status == result::UpdateStatus::kError) {
650 result =
result::Download(downloaded_targets, result::DownloadStatus::kError,
"Error rechecking stored metadata.");
651 storeInstallationFailure(
655 if (update_status != result::UpdateStatus::kUpdatesAvailable) {
656 sendEvent<event::AllDownloadsComplete>(
result);
660 for (
const auto &target : targets) {
661 auto res = downloadImage(target, token);
663 downloaded_targets.push_back(res.second);
667 if (targets.size() == downloaded_targets.size()) {
670 if (downloaded_targets.empty()) {
671 LOG_ERROR <<
"0 of " << targets.size() <<
" targets were successfully downloaded.";
672 result =
result::Download(downloaded_targets, result::DownloadStatus::kError,
"Each target download has failed");
674 LOG_ERROR <<
"Only " << downloaded_targets.size() <<
" of " << targets.size() <<
" were successfully downloaded.";
677 storeInstallationFailure(
681 sendEvent<event::AllDownloadsComplete>(
result);
685 void SotaUptaneClient::reportPause() {
686 const std::string &correlation_id = director_repo.getCorrelationId();
687 report_queue->enqueue(std_::make_unique<DevicePausedReport>(correlation_id));
690 void SotaUptaneClient::reportResume() {
691 const std::string &correlation_id = director_repo.getCorrelationId();
692 report_queue->enqueue(std_::make_unique<DeviceResumedReport>(correlation_id));
695 std::pair<bool, Uptane::Target> SotaUptaneClient::downloadImage(
const Uptane::Target &target,
697 const std::string &correlation_id = director_repo.getCorrelationId();
699 for (
const auto &ecu : target.ecus()) {
700 report_queue->enqueue(std_::make_unique<EcuDownloadStartedReport>(ecu.first, correlation_id));
707 bool success =
false;
709 KeyManager keys(storage, config.keymanagerConfig());
711 auto prog_cb = [
this](
const Uptane::Target &t,
const std::string &description,
unsigned int progress) {
712 report_progress_cb(events_channel.get(), t, description, progress);
717 if (target.IsForEcu(primary_ecu_serial) || !target.
IsOstree()) {
718 const int max_tries = 3;
720 std::chrono::milliseconds wait(500);
722 for (; tries < max_tries; tries++) {
723 success = package_manager_->fetchTarget(target, *uptane_fetcher, keys, prog_cb, token);
726 if (success || (token !=
nullptr && !token->
canContinue(
false))) {
728 }
else if (tries < max_tries - 1) {
729 std::this_thread::sleep_for(wait);
734 LOG_ERROR <<
"Download unsuccessful after " << tries <<
" attempts.";
744 }
catch (
const std::exception &e) {
745 LOG_ERROR <<
"Error downloading image: " << e.what();
746 last_exception = std::current_exception();
751 for (
const auto &ecu : target.ecus()) {
752 report_queue->enqueue(std_::make_unique<EcuDownloadCompletedReport>(ecu.first, correlation_id, success));
755 sendEvent<event::DownloadTargetComplete>(target, success);
756 return {success, target};
759 void SotaUptaneClient::uptaneIteration(std::vector<Uptane::Target> *targets,
unsigned int *ecus_count) {
760 updateDirectorMeta();
762 std::vector<Uptane::Target> tmp_targets;
765 getNewTargets(&tmp_targets, &ecus);
766 }
catch (
const std::exception &e) {
767 LOG_ERROR <<
"Inconsistency between Director metadata and available ECUs: " << e.what();
771 if (!tmp_targets.empty()) {
772 LOG_INFO <<
"New updates found in Director metadata. Checking Image repo metadata...";
776 if (targets !=
nullptr) {
777 *targets = std::move(tmp_targets);
779 if (ecus_count !=
nullptr) {
784 void SotaUptaneClient::uptaneOfflineIteration(std::vector<Uptane::Target> *targets,
unsigned int *ecus_count) {
785 checkDirectorMetaOffline();
787 std::vector<Uptane::Target> tmp_targets;
790 getNewTargets(&tmp_targets, &ecus);
791 }
catch (
const std::exception &e) {
792 LOG_ERROR <<
"Inconsistency between Director metadata and available ECUs: " << e.what();
796 if (!tmp_targets.empty()) {
797 LOG_DEBUG <<
"New updates found in stored Director metadata. Checking stored Image repo metadata...";
798 checkImageMetaOffline();
801 if (targets !=
nullptr) {
802 *targets = std::move(tmp_targets);
804 if (ecus_count !=
nullptr) {
809 void SotaUptaneClient::sendDeviceData(
const Json::Value &custom_hwinfo) {
810 reportHwInfo(custom_hwinfo);
811 reportInstalledPackages();
813 reportAktualizrConfiguration();
814 sendEvent<event::SendDeviceDataComplete>();
822 if (hasPendingUpdates()) {
824 LOG_INFO <<
"The current update is pending. Check if pending ECUs has been already updated";
825 checkAndUpdatePendingSecondaries();
828 if (hasPendingUpdates()) {
831 LOG_INFO <<
"An update is pending. Skipping check for update until installation is complete.";
833 "There are pending updates, no new updates are checked");
837 if (!putManifestSimple()) {
838 LOG_ERROR <<
"Error sending manifest!";
841 sendEvent<event::UpdateCheckComplete>(
result);
849 std::vector<Uptane::Target> updates;
850 unsigned int ecus_count = 0;
852 uptaneIteration(&updates, &ecus_count);
853 }
catch (
const std::exception &e) {
854 last_exception = std::current_exception();
859 std::string director_targets;
860 if (!storage->loadNonRoot(&director_targets, Uptane::RepositoryType::Director(), Uptane::Role::Targets())) {
865 if (updates.empty()) {
866 LOG_DEBUG <<
"No new updates found in Uptane metadata.";
868 result::UpdateCheck({}, 0, result::UpdateStatus::kNoUpdatesAvailable, Utils::parseJSON(director_targets),
"");
877 for (
auto &target : updates) {
878 auto image_target = findTargetInDelegationTree(target,
false);
879 if (image_target ==
nullptr) {
881 LOG_ERROR <<
"No matching target in Image repo Targets metadata for " << target;
886 if (target.uri().empty() && !image_target->uri().empty()) {
887 target.setUri(image_target->uri());
890 }
catch (
const std::exception &e) {
891 last_exception = std::current_exception();
894 storeInstallationFailure(
900 Utils::parseJSON(director_targets),
"");
901 if (updates.size() == 1) {
902 LOG_INFO <<
"1 new update found in both Director and Image repo metadata.";
904 LOG_INFO << updates.size() <<
" new updates found in both Director and Image repo metadata.";
909 result::UpdateStatus SotaUptaneClient::checkUpdatesOffline(
const std::vector<Uptane::Target> &targets) {
910 if (hasPendingUpdates()) {
912 LOG_INFO <<
"An update is pending. Skipping stored metadata check until installation is complete.";
913 return result::UpdateStatus::kError;
916 if (targets.empty()) {
917 LOG_WARNING <<
"Requested targets vector is empty. Nothing to do.";
918 return result::UpdateStatus::kError;
921 std::vector<Uptane::Target> director_targets;
922 unsigned int ecus_count = 0;
924 uptaneOfflineIteration(&director_targets, &ecus_count);
925 }
catch (
const std::exception &e) {
926 LOG_ERROR <<
"Aborting; invalid Uptane metadata in storage.";
930 if (director_targets.empty()) {
931 LOG_ERROR <<
"No new updates found while rechecking stored Director Targets metadata, but " << targets.size()
932 <<
" target(s) were requested.";
933 return result::UpdateStatus::kNoUpdatesAvailable;
939 for (
const auto &target : targets) {
941 const auto it = std::find_if(director_targets.cbegin(), director_targets.cend(), target_comp);
942 if (it == director_targets.cend()) {
943 LOG_ERROR <<
"No matching target in Director Targets metadata for " << target;
944 throw Uptane::Exception(Uptane::RepositoryType::DIRECTOR,
"No matching target in Director Targets metadata");
947 const auto image_target = findTargetInDelegationTree(target,
true);
948 if (image_target ==
nullptr) {
949 LOG_ERROR <<
"No matching target in Image repo Targets metadata for " << target;
950 throw Uptane::Exception(Uptane::RepositoryType::IMAGE,
"No matching target in Director Targets metadata");
954 return result::UpdateStatus::kUpdatesAvailable;
957 result::Install SotaUptaneClient::uptaneInstall(
const std::vector<Uptane::Target> &updates) {
958 const std::string &correlation_id = director_repo.getCorrelationId();
963 std::string raw_report;
965 std::tie(r, raw_report) = [
this, &updates, &correlation_id]() -> std::tuple<result::Install, std::string> {
972 update_status = checkUpdatesOffline(updates);
973 }
catch (
const std::exception &e) {
974 last_exception = std::current_exception();
975 update_status = result::UpdateStatus::kError;
978 if (update_status != result::UpdateStatus::kUpdatesAvailable) {
979 if (update_status == result::UpdateStatus::kNoUpdatesAvailable) {
984 return std::make_tuple(
result,
"Stored Uptane metadata is invalid");
989 for (
const auto &update : updates) {
990 if (update.IsForEcu(primary_ecu_serial) || !update.IsOstree()) {
996 if (package_manager_->verifyTarget(update) != TargetStatus::kGood) {
998 return std::make_tuple(
result,
"Downloaded target is invalid");
1007 if (!waitSecondariesReachable(updates)) {
1009 return std::make_tuple(
result,
"Secondaries were not available");
1013 std::vector<Uptane::Target> primary_updates = findForEcu(updates, primary_ecu_serial);
1018 sendMetadataToEcus(updates, &metadata_res, &rr);
1019 if (!metadata_res.isSuccess()) {
1020 result.dev_report = std::move(metadata_res);
1021 return std::make_tuple(
result, rr);
1025 if (!primary_updates.empty()) {
1028 primary_update.setCorrelationId(correlation_id);
1030 report_queue->enqueue(std_::make_unique<EcuInstallationStartedReport>(primary_ecu_serial, correlation_id));
1031 sendEvent<event::InstallStarted>(primary_ecu_serial);
1036 package_manager_->updateNotify();
1037 install_res = PackageInstallSetResult(primary_update);
1038 if (install_res.result_code.num_code == data::ResultCode::Numeric::kNeedCompletion) {
1040 report_queue->enqueue(std_::make_unique<EcuInstallationAppliedReport>(primary_ecu_serial, correlation_id));
1041 sendEvent<event::InstallTargetComplete>(primary_ecu_serial,
true);
1042 }
else if (install_res.result_code.num_code == data::ResultCode::Numeric::kOk) {
1043 storage->saveEcuInstallationResult(primary_ecu_serial, install_res);
1044 report_queue->enqueue(
1045 std_::make_unique<EcuInstallationCompletedReport>(primary_ecu_serial, correlation_id,
true));
1046 sendEvent<event::InstallTargetComplete>(primary_ecu_serial,
true);
1049 storage->saveEcuInstallationResult(primary_ecu_serial, install_res);
1050 report_queue->enqueue(
1051 std_::make_unique<EcuInstallationCompletedReport>(primary_ecu_serial, correlation_id,
false));
1052 sendEvent<event::InstallTargetComplete>(primary_ecu_serial,
false);
1054 result.ecu_reports.emplace(
result.ecu_reports.begin(), primary_update, primary_ecu_serial, install_res);
1056 LOG_INFO <<
"No update to install on Primary";
1059 auto sec_reports = sendImagesToEcus(updates);
1060 result.ecu_reports.insert(
result.ecu_reports.end(), sec_reports.begin(), sec_reports.end());
1061 computeDeviceInstallationResult(&
result.dev_report, &rr);
1063 return std::make_tuple(
result, rr);
1066 storage->storeDeviceInstallationResult(r.dev_report, raw_report, correlation_id);
1068 sendEvent<event::AllInstallsComplete>(r);
1074 auto campaigns = campaign::Campaign::fetchAvailableCampaigns(*http, config.tls.server);
1075 for (
const auto &c : campaigns) {
1076 LOG_INFO <<
"Campaign: " << c.name;
1077 LOG_INFO <<
"Campaign id: " << c.id;
1078 LOG_INFO <<
"Campaign size: " << c.size;
1079 LOG_INFO <<
"CampaignAccept required: " << (c.autoAccept ?
"no" :
"yes");
1080 LOG_INFO <<
"Message: " << c.description;
1083 sendEvent<event::CampaignCheckComplete>(
result);
1087 void SotaUptaneClient::campaignAccept(
const std::string &campaign_id) {
1088 sendEvent<event::CampaignAcceptComplete>();
1089 report_queue->enqueue(std_::make_unique<CampaignAcceptedReport>(campaign_id));
1092 void SotaUptaneClient::campaignDecline(
const std::string &campaign_id) {
1093 sendEvent<event::CampaignDeclineComplete>();
1094 report_queue->enqueue(std_::make_unique<CampaignDeclinedReport>(campaign_id));
1097 void SotaUptaneClient::campaignPostpone(
const std::string &campaign_id) {
1098 sendEvent<event::CampaignPostponeComplete>();
1099 report_queue->enqueue(std_::make_unique<CampaignPostponedReport>(campaign_id));
1102 bool SotaUptaneClient::isInstallCompletionRequired()
const {
1103 std::vector<std::pair<Uptane::EcuSerial, Hash>> pending_ecus;
1104 storage->getPendingEcus(&pending_ecus);
1105 bool pending_for_ecu = std::find_if(pending_ecus.begin(), pending_ecus.end(),
1106 [
this](
const std::pair<Uptane::EcuSerial, Hash> &ecu) ->
bool {
1107 return ecu.first == primary_ecu_serial_;
1108 }) != pending_ecus.end();
1110 return pending_for_ecu && config.uptane.force_install_completion;
1113 void SotaUptaneClient::completeInstall()
const {
1114 if (isInstallCompletionRequired()) {
1115 package_manager_->completeInstall();
1119 bool SotaUptaneClient::putManifestSimple(
const Json::Value &custom) {
1121 if (hasPendingUpdates()) {
1124 LOG_DEBUG <<
"An update is pending. Skipping manifest upload until installation is complete.";
1128 static bool connected =
true;
1129 auto manifest = AssembleManifest();
1130 if (!custom.empty()) {
1131 manifest[
"custom"] = custom;
1133 auto signed_manifest = uptane_manifest->sign(manifest);
1134 HttpResponse response = http->put(config.uptane.director_server +
"/manifest", signed_manifest);
1135 if (response.isOk()) {
1137 LOG_INFO <<
"Connectivity is restored.";
1140 storage->clearInstallationResults();
1147 LOG_WARNING <<
"Put manifest request failed: " << response.getStatusStr();
1151 bool SotaUptaneClient::putManifest(
const Json::Value &custom) {
1152 bool success = putManifestSimple(custom);
1153 sendEvent<event::PutManifestComplete>(success);
1157 bool SotaUptaneClient::waitSecondariesReachable(
const std::vector<Uptane::Target> &updates) {
1158 std::map<Uptane::EcuSerial, SecondaryInterface *> targeted_secondaries;
1160 for (
const auto &t : updates) {
1161 for (
const auto &ecu : t.ecus()) {
1162 if (ecu.first == primary_ecu_serial) {
1165 auto f = secondaries.find(ecu.first);
1166 if (f == secondaries.end()) {
1167 LOG_ERROR <<
"Target " << t <<
" has an unknown ECU serial.";
1171 targeted_secondaries[ecu.first] = f->second.get();
1175 if (targeted_secondaries.empty()) {
1179 LOG_INFO <<
"Waiting for Secondaries to connect to start installation...";
1181 auto deadline = std::chrono::system_clock::now() + std::chrono::seconds(config.uptane.secondary_preinstall_wait_sec);
1182 while (std::chrono::system_clock::now() <= deadline) {
1183 if (targeted_secondaries.empty()) {
1187 for (
auto sec_it = targeted_secondaries.begin(); sec_it != targeted_secondaries.end();) {
1188 bool connected =
false;
1190 connected = sec_it->second->ping();
1191 }
catch (
const std::exception &ex) {
1192 LOG_DEBUG <<
"Failed to ping Secondary with serial " << sec_it->first <<
": " << ex.what();
1195 sec_it = targeted_secondaries.erase(sec_it);
1200 std::this_thread::sleep_for(std::chrono::seconds(1));
1203 for (
const auto &sec : targeted_secondaries) {
1204 LOG_ERROR <<
"Secondary with serial " << sec.second->getSerial() <<
" failed to connect!";
1213 const std::string &correlation_id = director_repo.getCorrelationId();
1214 storage->storeDeviceInstallationResult(
result,
"", correlation_id);
1216 director_repo.dropTargets(*storage);
1223 std::string latest_root;
1224 if (!storage->loadLatestRoot(&latest_root, repo)) {
1225 LOG_ERROR <<
"Error reading Root metadata";
1230 const int last_root_version = Uptane::extractVersionUntrusted(latest_root);
1231 const int sec_root_version = secondary.getRootVersion((repo == Uptane::RepositoryType::Director()));
1232 if (sec_root_version > 0 && last_root_version - sec_root_version > 1) {
1233 for (
int v = sec_root_version + 1; v <= last_root_version; v++) {
1236 LOG_WARNING <<
"Couldn't find Root metadata in the storage, trying remote repo";
1238 uptane_fetcher->fetchRole(&root, Uptane::kMaxRootSize, repo, Uptane::Role::Root(),
Uptane::Version(v));
1239 }
catch (
const std::exception &e) {
1241 LOG_ERROR <<
"Root metadata could not be fetched, skipping to the next Secondary";
1243 "Root metadata could not be fetched, skipping to the next Secondary");
1248 result = secondary.putRoot(root, repo == Uptane::RepositoryType::Director());
1249 }
catch (
const std::exception &ex) {
1252 if (!
result.isSuccess()) {
1253 LOG_ERROR <<
"Sending metadata to " << secondary.getSerial() <<
" failed: " <<
result.result_code <<
" "
1264 std::string *raw_installation_report) {
1266 std::string result_code_err_str;
1267 for (
const auto &target : targets) {
1268 for (
const auto &ecu : target.ecus()) {
1271 auto sec = secondaries.find(ecu_serial);
1272 if (sec == secondaries.end()) {
1279 local_result = rotateSecondaryRoot(Uptane::RepositoryType::Director(), *(sec->second));
1280 if (!local_result.isSuccess()) {
1281 final_result = local_result;
1284 local_result = rotateSecondaryRoot(Uptane::RepositoryType::Image(), *(sec->second));
1285 if (!local_result.isSuccess()) {
1286 final_result = local_result;
1290 local_result = sec->second->putMetadata(target);
1291 }
catch (
const std::exception &ex) {
1295 if (!local_result.isSuccess()) {
1296 LOG_ERROR <<
"Sending metadata to " << sec->first <<
" failed: " << local_result.result_code <<
" "
1297 << local_result.description;
1298 const std::string ecu_code_str = hw_id.ToString() +
":" + local_result.result_code.toString();
1299 result_code_err_str += (!result_code_err_str.empty() ?
"|" :
"") + ecu_code_str;
1304 if (!result_code_err_str.empty()) {
1308 "Sending metadata to one or more ECUs failed");
1309 if (raw_installation_report !=
nullptr) {
1310 *raw_installation_report =
"Sending metadata to one or more ECUs failed";
1319 std::future<data::InstallationResult> SotaUptaneClient::sendFirmwareAsync(
SecondaryInterface &secondary,
1321 auto f = [
this, &secondary, target]() {
1322 const std::string &correlation_id = director_repo.getCorrelationId();
1324 sendEvent<event::InstallStarted>(secondary.getSerial());
1325 report_queue->enqueue(std_::make_unique<EcuInstallationStartedReport>(secondary.getSerial(), correlation_id));
1329 result = secondary.sendFirmware(target);
1330 if (
result.isSuccess()) {
1331 result = secondary.install(target);
1333 }
catch (
const std::exception &ex) {
1337 if (
result.result_code == data::ResultCode::Numeric::kNeedCompletion) {
1338 report_queue->enqueue(std_::make_unique<EcuInstallationAppliedReport>(secondary.getSerial(), correlation_id));
1340 report_queue->enqueue(
1341 std_::make_unique<EcuInstallationCompletedReport>(secondary.getSerial(), correlation_id,
result.isSuccess()));
1344 sendEvent<event::InstallTargetComplete>(secondary.getSerial(),
result.isSuccess());
1348 return std::async(std::launch::async, f);
1351 std::vector<result::Install::EcuReport> SotaUptaneClient::sendImagesToEcus(
const std::vector<Uptane::Target> &targets) {
1352 std::vector<result::Install::EcuReport> reports;
1353 std::vector<std::pair<result::Install::EcuReport, std::future<data::InstallationResult>>> firmwareFutures;
1357 for (
auto targets_it = targets.cbegin(); targets_it != targets.cend(); ++targets_it) {
1358 for (
auto ecus_it = targets_it->ecus().cbegin(); ecus_it != targets_it->ecus().cend(); ++ecus_it) {
1361 if (primary_ecu_serial == ecu_serial) {
1365 auto f = secondaries.find(ecu_serial);
1366 if (f == secondaries.end()) {
1367 LOG_ERROR <<
"Target " << *targets_it <<
" has an unknown ECU serial";
1373 sendFirmwareAsync(sec, *targets_it));
1377 for (
auto &f : firmwareFutures) {
1380 if (fut_result.isSuccess() || fut_result.result_code == data::ResultCode::Numeric::kNeedCompletion) {
1381 f.first.update.setCorrelationId(director_repo.getCorrelationId());
1383 fut_result.isSuccess() ? InstalledVersionUpdateMode::kCurrent : InstalledVersionUpdateMode::kPending;
1384 storage->saveInstalledVersion(f.first.serial.ToString(), f.first.update, update_mode);
1387 f.first.install_res = fut_result;
1388 storage->saveEcuInstallationResult(f.first.serial, f.first.install_res);
1389 reports.push_back(f.first);
1398 void SotaUptaneClient::checkAndUpdatePendingSecondaries() {
1399 std::vector<std::pair<Uptane::EcuSerial, Hash>> pending_ecus;
1400 storage->getPendingEcus(&pending_ecus);
1402 for (
const auto &pending_ecu : pending_ecus) {
1403 if (primaryEcuSerial() == pending_ecu.first) {
1406 auto &sec = secondaries[pending_ecu.first];
1409 manifest = sec->getManifest();
1410 }
catch (
const std::exception &ex) {
1411 LOG_DEBUG <<
"Failed to get manifest from Secondary with serial " << pending_ecu.first <<
": " << ex.what();
1414 if (manifest.empty()) {
1415 LOG_DEBUG <<
"Failed to get manifest from Secondary with serial " << pending_ecu.first;
1418 bool verified =
false;
1420 verified = manifest.verifySignature(sec->getPublicKey());
1421 }
catch (
const std::exception &ex) {
1422 LOG_ERROR <<
"Failed to get public key from Secondary with serial " << pending_ecu.first <<
": " << ex.what();
1425 LOG_ERROR <<
"Invalid manifest or signature reported by Secondary: "
1426 <<
" serial: " << pending_ecu.first <<
" manifest: " << manifest;
1429 auto current_ecu_hash = manifest.installedImageHash();
1430 if (pending_ecu.second == current_ecu_hash) {
1431 LOG_INFO <<
"The pending update " << current_ecu_hash <<
" has been installed on " << pending_ecu.first;
1432 boost::optional<Uptane::Target> pending_version;
1433 if (storage->loadInstalledVersions(pending_ecu.first.ToString(),
nullptr, &pending_version)) {
1434 storage->saveEcuInstallationResult(pending_ecu.first,
1437 storage->saveInstalledVersion(pending_ecu.first.ToString(), *pending_version,
1438 InstalledVersionUpdateMode::kCurrent);
1440 report_queue->enqueue(std_::make_unique<EcuInstallationCompletedReport>(
1441 pending_ecu.first, pending_version->correlation_id(),
true));
1444 std::string raw_report;
1445 computeDeviceInstallationResult(&ir, &raw_report);
1446 storage->storeDeviceInstallationResult(ir, raw_report, pending_version->correlation_id());
1452 boost::optional<Uptane::HardwareIdentifier> SotaUptaneClient::getEcuHwId(
const Uptane::EcuSerial &serial)
const {
1453 if (serial == primary_ecu_serial_ || serial.ToString().empty()) {
1454 if (primary_ecu_hw_id_ == Uptane::HardwareIdentifier::Unknown()) {
1457 return primary_ecu_hw_id_;
1460 const auto it = secondaries.find(serial);
1461 if (it != secondaries.end()) {
1462 return it->second->getHwId();