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()) {
43 result.push_back(*it);
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 const Json::Value network_info = Utils::getNetworkInfo();
198 const Hash new_hash = Hash::generate(Hash::Type::kSha256, Utils::jsonToCanonicalStr(network_info));
199 std::string stored_hash;
200 if (!(storage->loadDeviceDataHash(
"network_info", &stored_hash) &&
201 new_hash ==
Hash(Hash::Type::kSha256, stored_hash))) {
202 LOG_DEBUG <<
"Reporting network information";
203 const HttpResponse response = http->put(config.tls.server +
"/system_info/network", network_info);
204 if (response.isOk()) {
205 storage->storeDeviceDataHash(
"network_info", new_hash.HashString());
208 LOG_TRACE <<
"Not reporting network information because it has not changed";
212 void SotaUptaneClient::reportAktualizrConfiguration() {
213 if (!config.telemetry.report_config) {
214 LOG_TRACE <<
"Not reporting libaktualizr configuration because telemetry is disabled";
218 std::stringstream conf_ss;
219 config.writeToStream(conf_ss);
220 const std::string conf_str = conf_ss.str();
221 const Hash new_hash = Hash::generate(Hash::Type::kSha256, conf_str);
222 std::string stored_hash;
223 if (!(storage->loadDeviceDataHash(
"configuration", &stored_hash) &&
224 new_hash ==
Hash(Hash::Type::kSha256, stored_hash))) {
225 LOG_DEBUG <<
"Reporting libaktualizr configuration";
226 const HttpResponse response = http->post(config.tls.server +
"/system_info/config",
"application/toml", conf_str);
227 if (response.isOk()) {
228 storage->storeDeviceDataHash(
"configuration", new_hash.HashString());
231 LOG_TRACE <<
"Not reporting libaktualizr configuration because it has not changed";
235 Json::Value SotaUptaneClient::AssembleManifest() {
236 Json::Value manifest;
238 manifest[
"primary_ecu_serial"] = primary_ecu_serial.ToString();
241 Json::Value version_manifest;
243 Json::Value primary_manifest = uptane_manifest->assembleManifest(package_manager_->getCurrent());
244 std::vector<std::pair<Uptane::EcuSerial, int64_t>> ecu_cnt;
245 std::string report_counter;
246 if (!storage->loadEcuReportCounter(&ecu_cnt) || (ecu_cnt.size() == 0)) {
247 LOG_ERROR <<
"No ECU version report counter, please check the database!";
250 report_counter = std::to_string(ecu_cnt[0].second + 1);
251 storage->saveEcuReportCounter(ecu_cnt[0].first, ecu_cnt[0].second + 1);
253 version_manifest[primary_ecu_serial.ToString()] = uptane_manifest->sign(primary_manifest, report_counter);
255 for (
auto it = secondaries.begin(); it != secondaries.end(); it++) {
259 bool from_cache =
false;
260 if (secmanifest == Json::Value()) {
263 if (storage->loadCachedEcuManifest(ecu_serial, &cached)) {
264 LOG_WARNING <<
"Could not reach Secondary " << ecu_serial <<
", sending a cached version of its manifest";
265 secmanifest = Utils::parseJSON(cached);
268 LOG_ERROR <<
"Could not fetch a valid Secondary manifest from cache";
272 if (secmanifest.verifySignature(it->second->getPublicKey())) {
273 version_manifest[ecu_serial.ToString()] = secmanifest;
275 storage->storeCachedEcuManifest(ecu_serial, Utils::jsonToCanonicalStr(secmanifest));
279 LOG_ERROR <<
"Secondary manifest is corrupted or not signed, or signature is invalid manifest: " << secmanifest;
282 manifest[
"ecu_version_manifests"] = version_manifest;
285 Json::Value installation_report;
288 std::string raw_report;
289 std::string correlation_id;
290 bool has_results = storage->loadDeviceInstallationResult(&dev_result, &raw_report, &correlation_id);
292 if (!(dev_result.isSuccess() || dev_result.needCompletion())) {
293 director_repo.dropTargets(*storage);
296 installation_report[
"result"] = dev_result.toJson();
297 installation_report[
"raw_report"] = raw_report;
298 installation_report[
"correlation_id"] = correlation_id;
299 installation_report[
"items"] = Json::arrayValue;
301 std::vector<std::pair<Uptane::EcuSerial, data::InstallationResult>> ecu_results;
302 storage->loadEcuInstallationResults(&ecu_results);
303 for (
const auto &r : ecu_results) {
308 item[
"ecu"] = serial.ToString();
309 item[
"result"] = res.toJson();
311 installation_report[
"items"].append(item);
314 manifest[
"installation_report"][
"content_type"] =
"application/vnd.com.here.otac.installationReport.v1";
315 manifest[
"installation_report"][
"report"] = installation_report;
317 LOG_DEBUG <<
"No installation result to report in manifest";
323 bool SotaUptaneClient::hasPendingUpdates()
const {
return storage->hasPendingInstall(); }
325 void SotaUptaneClient::initialize() {
326 LOG_DEBUG <<
"Checking if device is provisioned...";
327 auto keys = std::make_shared<KeyManager>(storage, config.keymanagerConfig());
329 Initializer initializer(config.provision, storage, http, *keys, secondaries);
333 if (!storage->loadEcuSerials(&serials) || serials.size() == 0) {
334 throw std::runtime_error(
"Unable to load ECU serials after device registration.");
337 uptane_manifest = std::make_shared<Uptane::ManifestIssuer>(keys, serials[0].first);
338 primary_ecu_serial_ = serials[0].first;
339 primary_ecu_hw_id_ = serials[0].second;
340 LOG_INFO <<
"Primary ECU serial: " << primary_ecu_serial_ <<
" with hardware ID: " << primary_ecu_hw_id_;
342 for (
auto it = secondaries.begin(); it != secondaries.end(); ++it) {
343 it->second->init(secondary_provider_);
346 std::string device_id;
347 if (!storage->loadDeviceId(&device_id)) {
348 throw std::runtime_error(
"Unable to load device ID after device registration.");
350 LOG_INFO <<
"Device ID: " << device_id;
351 LOG_INFO <<
"Device Gateway URL: " << config.tls.server;
355 std::string not_before;
356 std::string not_after;
357 keys->getCertInfo(&subject, &issuer, ¬_before, ¬_after);
358 LOG_INFO <<
"Certificate subject: " << subject;
359 LOG_INFO <<
"Certificate issuer: " << issuer;
360 LOG_INFO <<
"Certificate valid from: " << not_before <<
" until: " << not_after;
362 LOG_DEBUG <<
"... provisioned OK";
363 finalizeAfterReboot();
366 void SotaUptaneClient::updateDirectorMeta() {
368 director_repo.updateMeta(*storage, *uptane_fetcher);
369 }
catch (
const std::exception &e) {
370 LOG_ERROR <<
"Director metadata update failed: " << e.what();
375 void SotaUptaneClient::updateImageMeta() {
377 image_repo.updateMeta(*storage, *uptane_fetcher);
378 }
catch (
const std::exception &e) {
379 LOG_ERROR <<
"Failed to update Image repo metadata: " << e.what();
384 void SotaUptaneClient::checkDirectorMetaOffline() {
386 director_repo.checkMetaOffline(*storage);
387 }
catch (
const std::exception &e) {
388 LOG_ERROR <<
"Failed to check Director metadata: " << e.what();
393 void SotaUptaneClient::checkImageMetaOffline() {
395 image_repo.checkMetaOffline(*storage);
396 }
catch (
const std::exception &e) {
397 LOG_ERROR <<
"Failed to check Image repo metadata: " << e.what();
402 std::string *raw_installation_report)
const {
405 std::string raw_ir =
"Installation succesful";
408 std::vector<std::pair<Uptane::EcuSerial, data::InstallationResult>> ecu_results;
410 if (!storage->loadEcuInstallationResults(&ecu_results)) {
413 "Unable to get installation results from ECUs");
414 raw_ir =
"Failed to load ECU installation results";
418 std::string result_code_err_str;
420 for (
const auto &r : ecu_results) {
421 auto ecu_serial = r.first;
422 auto installation_res = r.second;
424 auto hw_id = getEcuHwId(ecu_serial);
429 "Unable to get installation results from ECUs");
431 raw_ir =
"Failed to find an ECU with the given serial: " + ecu_serial.ToString();
435 if (installation_res.needCompletion()) {
437 device_installation_result =
439 "ECU needs completion/finalization to be installed: " + ecu_serial.ToString());
440 raw_ir =
"ECU needs completion/finalization to be installed: " + ecu_serial.ToString();
446 if (!installation_res.isSuccess()) {
447 const std::string ecu_code_str = (*hw_id).ToString() +
":" + installation_res.result_code.toString();
448 result_code_err_str += (result_code_err_str !=
"" ?
"|" :
"") + ecu_code_str;
452 if (!result_code_err_str.empty()) {
454 device_installation_result =
456 "Installation failed on one or more ECUs");
457 raw_ir =
"Installation failed on one or more ECUs";
464 if (result !=
nullptr) {
465 *result = device_installation_result;
468 if (raw_installation_report !=
nullptr) {
469 *raw_installation_report = raw_ir;
473 void SotaUptaneClient::getNewTargets(std::vector<Uptane::Target> *new_targets,
unsigned int *ecus_count) {
474 const std::vector<Uptane::Target> targets = director_repo.getTargets().targets;
476 if (ecus_count !=
nullptr) {
481 for (
const auto &ecu : target.ecus()) {
489 const auto hw_id_known = getEcuHwId(ecu_serial);
494 LOG_ERROR <<
"Unknown ECU ID in Director Targets metadata: " << ecu_serial.ToString();
498 if (*hw_id_known != hw_id) {
499 LOG_ERROR <<
"Wrong hardware identifier for ECU " << ecu_serial.ToString();
503 boost::optional<Uptane::Target> current_version;
504 if (!storage->loadInstalledVersions(ecu_serial.ToString(), ¤t_version,
nullptr)) {
505 LOG_WARNING <<
"Could not load currently installed version for ECU ID: " << ecu_serial.ToString();
509 if (!current_version) {
510 LOG_WARNING <<
"Current version for ECU ID: " << ecu_serial.ToString() <<
" is unknown";
512 }
else if (current_version->MatchTarget(target)) {
514 }
else if (current_version->filename() == target.filename()) {
515 LOG_ERROR <<
"Director Target filename matches currently installed version, but content differs!";
524 if (primary_ecu_serial == ecu_serial) {
526 (config.pacman.type == PACKAGE_MANAGER_OSTREE || config.pacman.type == PACKAGE_MANAGER_OSTREEDOCKERAPP)) {
527 LOG_ERROR <<
"Cannot install a non-OSTree package on an OSTree system";
532 if (is_new && ecus_count !=
nullptr) {
538 new_targets->push_back(target);
543 std::unique_ptr<Uptane::Target> SotaUptaneClient::findTargetHelper(
const Uptane::Targets &cur_targets,
545 const int level,
const bool terminating,
546 const bool offline) {
548 const auto it = std::find_if(cur_targets.targets.cbegin(), cur_targets.targets.cend(), target_comp);
549 if (it != cur_targets.targets.cend()) {
550 return std_::make_unique<Uptane::Target>(*it);
553 if (terminating || level >= Uptane::kDelegationsMaxDepth) {
554 return std::unique_ptr<Uptane::Target>(
nullptr);
557 for (
const auto &delegate_name : cur_targets.delegated_role_names_) {
558 Uptane::Role delegate_role = Uptane::Role::Delegation(delegate_name);
559 auto patterns = cur_targets.paths_for_role_.find(delegate_role);
560 if (patterns == cur_targets.paths_for_role_.end()) {
565 for (
const auto &pattern : patterns->second) {
566 if (fnmatch(pattern.c_str(), queried_target.filename().c_str(), 0) == 0) {
578 Uptane::getTrustedDelegation(delegate_role, cur_targets, image_repo, *storage, *uptane_fetcher, offline);
579 if (delegation.isExpired(TimeStamp::Now())) {
583 auto is_terminating = cur_targets.terminating_role_.find(delegate_role);
584 if (is_terminating == cur_targets.terminating_role_.end()) {
588 auto found_target = findTargetHelper(delegation, queried_target, level + 1, is_terminating->second, offline);
589 if (found_target !=
nullptr) {
594 return std::unique_ptr<Uptane::Target>(
nullptr);
597 std::unique_ptr<Uptane::Target> SotaUptaneClient::findTargetInDelegationTree(
const Uptane::Target &target,
598 const bool offline) {
599 auto toplevel_targets = image_repo.getTargets();
600 if (toplevel_targets ==
nullptr) {
601 return std::unique_ptr<Uptane::Target>(
nullptr);
604 return findTargetHelper(*toplevel_targets, target, 0,
false, offline);
607 result::Download SotaUptaneClient::downloadImages(
const std::vector<Uptane::Target> &targets,
611 std::lock_guard<std::mutex> guard(download_mutex);
613 std::vector<Uptane::Target> downloaded_targets;
617 update_status = checkUpdatesOffline(targets);
618 }
catch (
const std::exception &e) {
619 last_exception = std::current_exception();
620 update_status = result::UpdateStatus::kError;
623 if (update_status == result::UpdateStatus::kNoUpdatesAvailable) {
624 result =
result::Download({}, result::DownloadStatus::kNothingToDownload,
"");
625 }
else if (update_status == result::UpdateStatus::kError) {
626 result =
result::Download(downloaded_targets, result::DownloadStatus::kError,
"Error rechecking stored metadata.");
627 storeInstallationFailure(
631 if (update_status != result::UpdateStatus::kUpdatesAvailable) {
632 sendEvent<event::AllDownloadsComplete>(result);
636 for (
const auto &target : targets) {
637 auto res = downloadImage(target, token);
639 downloaded_targets.push_back(res.second);
643 if (targets.size() == downloaded_targets.size()) {
644 result =
result::Download(downloaded_targets, result::DownloadStatus::kSuccess,
"");
646 if (downloaded_targets.size() == 0) {
647 LOG_ERROR <<
"0 of " << targets.size() <<
" targets were successfully downloaded.";
648 result =
result::Download(downloaded_targets, result::DownloadStatus::kError,
"Each target download has failed");
650 LOG_ERROR <<
"Only " << downloaded_targets.size() <<
" of " << targets.size() <<
" were successfully downloaded.";
651 result =
result::Download(downloaded_targets, result::DownloadStatus::kPartialSuccess,
"");
653 storeInstallationFailure(
657 sendEvent<event::AllDownloadsComplete>(result);
661 void SotaUptaneClient::reportPause() {
662 const std::string &correlation_id = director_repo.getCorrelationId();
663 report_queue->enqueue(std_::make_unique<DevicePausedReport>(correlation_id));
666 void SotaUptaneClient::reportResume() {
667 const std::string &correlation_id = director_repo.getCorrelationId();
668 report_queue->enqueue(std_::make_unique<DeviceResumedReport>(correlation_id));
671 std::pair<bool, Uptane::Target> SotaUptaneClient::downloadImage(
const Uptane::Target &target,
675 const std::string &correlation_id = director_repo.getCorrelationId();
677 for (
const auto &ecu : target.ecus()) {
678 report_queue->enqueue(std_::make_unique<EcuDownloadStartedReport>(ecu.first, correlation_id));
685 bool success =
false;
687 KeyManager keys(storage, config.keymanagerConfig());
689 auto prog_cb = [
this](
const Uptane::Target &t,
const std::string &description,
unsigned int progress) {
690 report_progress_cb(events_channel.get(), t, description, progress);
695 if (target.IsForEcu(primary_ecu_serial) || !target.
IsOstree()) {
697 const int max_tries = 3;
699 std::chrono::milliseconds wait(500);
701 for (; tries < max_tries; tries++) {
702 success = package_manager_->fetchTarget(target, *uptane_fetcher, keys, prog_cb, token);
705 if (success || (token !=
nullptr && !token->
canContinue(
false))) {
707 }
else if (tries < max_tries - 1) {
708 std::this_thread::sleep_for(wait);
713 LOG_ERROR <<
"Download unsuccessful after " << tries <<
" attempts.";
721 }
catch (
const std::exception &e) {
722 LOG_ERROR <<
"Error downloading image: " << e.what();
723 last_exception = std::current_exception();
728 for (
const auto &ecu : target.ecus()) {
729 report_queue->enqueue(std_::make_unique<EcuDownloadCompletedReport>(ecu.first, correlation_id, success));
732 sendEvent<event::DownloadTargetComplete>(target, success);
733 return {success, target};
736 void SotaUptaneClient::uptaneIteration(std::vector<Uptane::Target> *targets,
unsigned int *ecus_count) {
737 updateDirectorMeta();
739 std::vector<Uptane::Target> tmp_targets;
742 getNewTargets(&tmp_targets, &ecus);
743 }
catch (
const std::exception &e) {
744 LOG_ERROR <<
"Inconsistency between Director metadata and available ECUs: " << e.what();
748 if (!tmp_targets.empty()) {
749 LOG_INFO <<
"New updates found in Director metadata. Checking Image repo metadata...";
753 if (targets !=
nullptr) {
754 *targets = std::move(tmp_targets);
756 if (ecus_count !=
nullptr) {
761 void SotaUptaneClient::uptaneOfflineIteration(std::vector<Uptane::Target> *targets,
unsigned int *ecus_count) {
762 checkDirectorMetaOffline();
764 std::vector<Uptane::Target> tmp_targets;
767 getNewTargets(&tmp_targets, &ecus);
768 }
catch (
const std::exception &e) {
769 LOG_ERROR <<
"Inconsistency between Director metadata and available ECUs: " << e.what();
773 if (!tmp_targets.empty()) {
774 LOG_DEBUG <<
"New updates found in stored Director metadata. Checking stored Image repo metadata...";
775 checkImageMetaOffline();
778 if (targets !=
nullptr) {
779 *targets = std::move(tmp_targets);
781 if (ecus_count !=
nullptr) {
786 void SotaUptaneClient::sendDeviceData(
const Json::Value &custom_hwinfo) {
787 reportHwInfo(custom_hwinfo);
788 reportInstalledPackages();
790 reportAktualizrConfiguration();
791 sendEvent<event::SendDeviceDataComplete>();
799 if (hasPendingUpdates()) {
801 LOG_INFO <<
"The current update is pending. Check if pending ECUs has been already updated";
802 checkAndUpdatePendingSecondaries();
805 if (hasPendingUpdates()) {
808 LOG_INFO <<
"An update is pending. Skipping check for update until installation is complete.";
810 "There are pending updates, no new updates are checked");
814 if (!putManifestSimple()) {
815 LOG_ERROR <<
"Error sending manifest!";
817 result = checkUpdates();
818 sendEvent<event::UpdateCheckComplete>(result);
826 std::vector<Uptane::Target> updates;
827 unsigned int ecus_count = 0;
829 uptaneIteration(&updates, &ecus_count);
830 }
catch (
const std::exception &e) {
831 last_exception = std::current_exception();
832 result =
result::UpdateCheck({}, 0, result::UpdateStatus::kError, Json::nullValue,
"Could not update metadata.");
836 std::string director_targets;
837 if (!storage->loadNonRoot(&director_targets, Uptane::RepositoryType::Director(), Uptane::Role::Targets())) {
839 throw std::runtime_error(
"Could not get Director's Targets from storage");
842 if (updates.empty()) {
843 LOG_DEBUG <<
"No new updates found in Uptane metadata.";
845 result::UpdateCheck({}, 0, result::UpdateStatus::kNoUpdatesAvailable, Utils::parseJSON(director_targets),
"");
854 for (
auto &target : updates) {
855 auto image_target = findTargetInDelegationTree(target,
false);
856 if (image_target ==
nullptr) {
858 LOG_ERROR <<
"No matching target in Image repo Targets metadata for " << target;
863 if (target.uri().empty() && !image_target->uri().empty()) {
864 target.setUri(image_target->uri());
867 }
catch (
const std::exception &e) {
868 last_exception = std::current_exception();
869 result =
result::UpdateCheck({}, 0, result::UpdateStatus::kError, Utils::parseJSON(director_targets),
871 storeInstallationFailure(
877 Utils::parseJSON(director_targets),
"");
878 if (updates.size() == 1) {
879 LOG_INFO <<
"1 new update found in both Director and Image repo metadata.";
881 LOG_INFO << updates.size() <<
" new updates found in both Director and Image repo metadata.";
886 result::UpdateStatus SotaUptaneClient::checkUpdatesOffline(
const std::vector<Uptane::Target> &targets) {
887 if (hasPendingUpdates()) {
889 LOG_INFO <<
"An update is pending. Skipping stored metadata check until installation is complete.";
890 return result::UpdateStatus::kError;
893 if (targets.empty()) {
894 LOG_WARNING <<
"Requested targets vector is empty. Nothing to do.";
895 return result::UpdateStatus::kError;
898 std::vector<Uptane::Target> director_targets;
899 unsigned int ecus_count = 0;
901 uptaneOfflineIteration(&director_targets, &ecus_count);
902 }
catch (
const std::exception &e) {
903 LOG_ERROR <<
"Aborting; invalid Uptane metadata in storage.";
907 if (director_targets.empty()) {
908 LOG_ERROR <<
"No new updates found while rechecking stored Director Targets metadata, but " << targets.size()
909 <<
" target(s) were requested.";
910 return result::UpdateStatus::kNoUpdatesAvailable;
916 for (
const auto &target : targets) {
918 const auto it = std::find_if(director_targets.cbegin(), director_targets.cend(), target_comp);
919 if (it == director_targets.cend()) {
920 LOG_ERROR <<
"No matching target in Director Targets metadata for " << target;
921 throw Uptane::Exception(Uptane::RepositoryType::DIRECTOR,
"No matching target in Director Targets metadata");
924 const auto image_target = findTargetInDelegationTree(target,
true);
925 if (image_target ==
nullptr) {
926 LOG_ERROR <<
"No matching target in Image repo Targets metadata for " << target;
927 throw Uptane::Exception(Uptane::RepositoryType::IMAGE,
"No matching target in Director Targets metadata");
931 return result::UpdateStatus::kUpdatesAvailable;
934 result::Install SotaUptaneClient::uptaneInstall(
const std::vector<Uptane::Target> &updates) {
935 const std::string &correlation_id = director_repo.getCorrelationId();
940 std::string raw_report;
942 std::tie(r, raw_report) = [
this, &updates, &correlation_id]() -> std::tuple<result::Install, std::string> {
949 update_status = checkUpdatesOffline(updates);
950 }
catch (
const std::exception &e) {
951 last_exception = std::current_exception();
952 update_status = result::UpdateStatus::kError;
955 if (update_status != result::UpdateStatus::kUpdatesAvailable) {
956 if (update_status == result::UpdateStatus::kNoUpdatesAvailable) {
961 return std::make_tuple(result,
"Stored Uptane metadata is invalid");
966 for (
const auto &update : updates) {
967 if (update.IsForEcu(primary_ecu_serial) || !update.IsOstree()) {
973 if (package_manager_->verifyTarget(update) != TargetStatus::kGood) {
975 return std::make_tuple(result,
"Downloaded target is invalid");
984 if (!waitSecondariesReachable(updates)) {
986 return std::make_tuple(result,
"Secondaries were not available");
990 std::vector<Uptane::Target> primary_updates = findForEcu(updates, primary_ecu_serial);
995 sendMetadataToEcus(updates, &metadata_res, &rr);
996 if (!metadata_res.isSuccess()) {
997 result.dev_report = std::move(metadata_res);
998 return std::make_tuple(result, rr);
1002 if (primary_updates.size() != 0u) {
1005 primary_update.setCorrelationId(correlation_id);
1007 report_queue->enqueue(std_::make_unique<EcuInstallationStartedReport>(primary_ecu_serial, correlation_id));
1008 sendEvent<event::InstallStarted>(primary_ecu_serial);
1013 package_manager_->updateNotify();
1014 install_res = PackageInstallSetResult(primary_update);
1015 if (install_res.result_code.num_code == data::ResultCode::Numeric::kNeedCompletion) {
1017 report_queue->enqueue(std_::make_unique<EcuInstallationAppliedReport>(primary_ecu_serial, correlation_id));
1018 sendEvent<event::InstallTargetComplete>(primary_ecu_serial,
true);
1019 }
else if (install_res.result_code.num_code == data::ResultCode::Numeric::kOk) {
1020 storage->saveEcuInstallationResult(primary_ecu_serial, install_res);
1021 report_queue->enqueue(
1022 std_::make_unique<EcuInstallationCompletedReport>(primary_ecu_serial, correlation_id,
true));
1023 sendEvent<event::InstallTargetComplete>(primary_ecu_serial,
true);
1026 storage->saveEcuInstallationResult(primary_ecu_serial, install_res);
1027 report_queue->enqueue(
1028 std_::make_unique<EcuInstallationCompletedReport>(primary_ecu_serial, correlation_id,
false));
1029 sendEvent<event::InstallTargetComplete>(primary_ecu_serial,
false);
1031 result.ecu_reports.emplace(result.ecu_reports.begin(), primary_update, primary_ecu_serial, install_res);
1033 LOG_INFO <<
"No update to install on Primary";
1036 auto sec_reports = sendImagesToEcus(updates);
1037 result.ecu_reports.insert(result.ecu_reports.end(), sec_reports.begin(), sec_reports.end());
1038 computeDeviceInstallationResult(&result.dev_report, &rr);
1040 return std::make_tuple(result, rr);
1044 storage->storeDeviceInstallationResult(r.dev_report, raw_report, correlation_id);
1046 sendEvent<event::AllInstallsComplete>(r);
1052 auto campaigns = campaign::Campaign::fetchAvailableCampaigns(*http, config.tls.server);
1053 for (
const auto &c : campaigns) {
1054 LOG_INFO <<
"Campaign: " << c.name;
1055 LOG_INFO <<
"Campaign id: " << c.id;
1056 LOG_INFO <<
"Campaign size: " << c.size;
1057 LOG_INFO <<
"CampaignAccept required: " << (c.autoAccept ?
"no" :
"yes");
1058 LOG_INFO <<
"Message: " << c.description;
1061 sendEvent<event::CampaignCheckComplete>(result);
1065 void SotaUptaneClient::campaignAccept(
const std::string &campaign_id) {
1066 sendEvent<event::CampaignAcceptComplete>();
1067 report_queue->enqueue(std_::make_unique<CampaignAcceptedReport>(campaign_id));
1070 void SotaUptaneClient::campaignDecline(
const std::string &campaign_id) {
1071 sendEvent<event::CampaignDeclineComplete>();
1072 report_queue->enqueue(std_::make_unique<CampaignDeclinedReport>(campaign_id));
1075 void SotaUptaneClient::campaignPostpone(
const std::string &campaign_id) {
1076 sendEvent<event::CampaignPostponeComplete>();
1077 report_queue->enqueue(std_::make_unique<CampaignPostponedReport>(campaign_id));
1080 bool SotaUptaneClient::isInstallCompletionRequired()
const {
1081 std::vector<std::pair<Uptane::EcuSerial, Hash>> pending_ecus;
1082 storage->getPendingEcus(&pending_ecus);
1083 bool pending_for_ecu = std::find_if(pending_ecus.begin(), pending_ecus.end(),
1084 [
this](
const std::pair<Uptane::EcuSerial, Hash> &ecu) ->
bool {
1085 return ecu.first == primary_ecu_serial_;
1086 }) != pending_ecus.end();
1088 return pending_for_ecu && config.uptane.force_install_completion;
1091 void SotaUptaneClient::completeInstall()
const {
1092 if (isInstallCompletionRequired()) {
1093 package_manager_->completeInstall();
1097 bool SotaUptaneClient::putManifestSimple(
const Json::Value &custom) {
1099 if (hasPendingUpdates()) {
1102 LOG_DEBUG <<
"An update is pending. Skipping manifest upload until installation is complete.";
1106 static bool connected =
true;
1107 auto manifest = AssembleManifest();
1108 if (custom != Json::nullValue) {
1109 manifest[
"custom"] = custom;
1111 auto signed_manifest = uptane_manifest->sign(manifest);
1112 HttpResponse response = http->put(config.uptane.director_server +
"/manifest", signed_manifest);
1113 if (response.isOk()) {
1115 LOG_INFO <<
"Connectivity is restored.";
1118 storage->clearInstallationResults();
1125 LOG_WARNING <<
"Put manifest request failed: " << response.getStatusStr();
1129 bool SotaUptaneClient::putManifest(
const Json::Value &custom) {
1130 bool success = putManifestSimple(custom);
1131 sendEvent<event::PutManifestComplete>(success);
1135 bool SotaUptaneClient::waitSecondariesReachable(
const std::vector<Uptane::Target> &updates) {
1136 std::map<Uptane::EcuSerial, SecondaryInterface *> targeted_secondaries;
1138 for (
const auto &t : updates) {
1139 for (
const auto &ecu : t.ecus()) {
1140 if (ecu.first == primary_ecu_serial) {
1143 auto f = secondaries.find(ecu.first);
1144 if (f == secondaries.end()) {
1145 LOG_ERROR <<
"Target " << t <<
" has unknown ECU ID";
1149 targeted_secondaries[ecu.first] = f->second.get();
1153 if (targeted_secondaries.empty()) {
1157 LOG_INFO <<
"Waiting for Secondaries to connect to start installation...";
1159 auto deadline = std::chrono::system_clock::now() + std::chrono::seconds(config.uptane.secondary_preinstall_wait_sec);
1160 while (std::chrono::system_clock::now() <= deadline) {
1161 if (targeted_secondaries.empty()) {
1165 for (
auto sec_it = targeted_secondaries.begin(); sec_it != targeted_secondaries.end();) {
1166 if (sec_it->second->ping()) {
1167 sec_it = targeted_secondaries.erase(sec_it);
1172 std::this_thread::sleep_for(std::chrono::seconds(1));
1175 LOG_ERROR <<
"Secondaries did not connect: ";
1177 for (
const auto &sec : targeted_secondaries) {
1178 LOG_ERROR << sec.second->getSerial();
1187 const std::string &correlation_id = director_repo.getCorrelationId();
1188 storage->storeDeviceInstallationResult(result,
"", correlation_id);
1190 director_repo.dropTargets(*storage);
1197 std::string latest_root;
1198 if (!storage->loadLatestRoot(&latest_root, repo)) {
1199 LOG_ERROR <<
"Error reading Root metadata";
1204 const int last_root_version = Uptane::extractVersionUntrusted(latest_root);
1205 const int sec_root_version = secondary.getRootVersion((repo == Uptane::RepositoryType::Director()));
1206 if (sec_root_version > 0 && last_root_version - sec_root_version > 1) {
1207 for (
int v = sec_root_version + 1; v <= last_root_version; v++) {
1210 LOG_WARNING <<
"Couldn't find Root metadata in the storage, trying remote repo";
1212 uptane_fetcher->fetchRole(&root, Uptane::kMaxRootSize, repo, Uptane::Role::Root(),
Uptane::Version(v));
1213 }
catch (
const std::exception &e) {
1215 LOG_ERROR <<
"Root metadata could not be fetched, skipping to the next Secondary";
1217 "Root metadata could not be fetched, skipping to the next Secondary");
1221 result = secondary.putRoot(root, repo == Uptane::RepositoryType::Director());
1222 if (!result.isSuccess()) {
1223 LOG_ERROR <<
"Sending metadata to " << secondary.getSerial() <<
" failed: " << result.result_code <<
" " 1224 << result.description;
1233 void SotaUptaneClient::sendMetadataToEcus(
const std::vector<Uptane::Target> &targets,
data::InstallationResult *result,
1234 std::string *raw_installation_report) {
1236 std::string result_code_err_str;
1237 for (
const auto &target : targets) {
1238 for (
const auto &ecu : target.ecus()) {
1241 auto sec = secondaries.find(ecu_serial);
1242 if (sec == secondaries.end()) {
1249 local_result = rotateSecondaryRoot(Uptane::RepositoryType::Director(), *(sec->second));
1250 if (!local_result.isSuccess()) {
1251 final_result = local_result;
1254 local_result = rotateSecondaryRoot(Uptane::RepositoryType::Image(), *(sec->second));
1255 if (!local_result.isSuccess()) {
1256 final_result = local_result;
1259 local_result = sec->second->putMetadata(target);
1261 if (!local_result.isSuccess()) {
1262 LOG_ERROR <<
"Sending metadata to " << sec->first <<
" failed: " << local_result.result_code <<
" " 1263 << local_result.description;
1264 const std::string ecu_code_str = hw_id.ToString() +
":" + local_result.result_code.toString();
1265 result_code_err_str += (result_code_err_str !=
"" ?
"|" :
"") + ecu_code_str;
1270 if (!result_code_err_str.empty()) {
1274 "Sending metadata to one or more ECUs failed");
1275 if (raw_installation_report !=
nullptr) {
1276 *raw_installation_report =
"Sending metadata to one or more ECUs failed";
1280 if (result !=
nullptr) {
1281 *result = final_result;
1285 std::future<data::InstallationResult> SotaUptaneClient::sendFirmwareAsync(
SecondaryInterface &secondary,
1287 auto f = [
this, &secondary, target]() {
1288 const std::string &correlation_id = director_repo.getCorrelationId();
1290 sendEvent<event::InstallStarted>(secondary.getSerial());
1291 report_queue->enqueue(std_::make_unique<EcuInstallationStartedReport>(secondary.getSerial(), correlation_id));
1295 if (result.isSuccess()) {
1296 result = secondary.install(target);
1299 if (result.result_code == data::ResultCode::Numeric::kNeedCompletion) {
1300 report_queue->enqueue(std_::make_unique<EcuInstallationAppliedReport>(secondary.getSerial(), correlation_id));
1302 report_queue->enqueue(
1303 std_::make_unique<EcuInstallationCompletedReport>(secondary.getSerial(), correlation_id, result.isSuccess()));
1306 sendEvent<event::InstallTargetComplete>(secondary.getSerial(), result.isSuccess());
1310 return std::async(std::launch::async, f);
1313 std::vector<result::Install::EcuReport> SotaUptaneClient::sendImagesToEcus(
const std::vector<Uptane::Target> &targets) {
1314 std::vector<result::Install::EcuReport> reports;
1315 std::vector<std::pair<result::Install::EcuReport, std::future<data::InstallationResult>>> firmwareFutures;
1319 for (
auto targets_it = targets.cbegin(); targets_it != targets.cend(); ++targets_it) {
1320 for (
auto ecus_it = targets_it->ecus().cbegin(); ecus_it != targets_it->ecus().cend(); ++ecus_it) {
1323 if (primary_ecu_serial == ecu_serial) {
1327 auto f = secondaries.find(ecu_serial);
1328 if (f == secondaries.end()) {
1329 LOG_ERROR <<
"Target " << *targets_it <<
" has unknown ECU ID";
1335 sendFirmwareAsync(sec, *targets_it));
1339 for (
auto &f : firmwareFutures) {
1342 if (fut_result.isSuccess() || fut_result.result_code == data::ResultCode::Numeric::kNeedCompletion) {
1343 f.first.update.setCorrelationId(director_repo.getCorrelationId());
1345 fut_result.isSuccess() ? InstalledVersionUpdateMode::kCurrent : InstalledVersionUpdateMode::kPending;
1346 storage->saveInstalledVersion(f.first.serial.ToString(), f.first.update, update_mode);
1349 f.first.install_res = fut_result;
1350 storage->saveEcuInstallationResult(f.first.serial, f.first.install_res);
1351 reports.push_back(f.first);
1360 void SotaUptaneClient::checkAndUpdatePendingSecondaries() {
1362 std::vector<std::pair<Uptane::EcuSerial, Hash>> pending_ecus;
1363 storage->getPendingEcus(&pending_ecus);
1365 for (
const auto &pending_ecu : pending_ecus) {
1366 if (primaryEcuSerial() == pending_ecu.first) {
1369 auto &sec = secondaries[pending_ecu.first];
1370 const auto &manifest = sec->getManifest();
1371 if (manifest == Json::nullValue) {
1372 LOG_DEBUG <<
"Failed to get a manifest from Secondary: " << sec->getSerial();
1375 if (!manifest.verifySignature(sec->getPublicKey())) {
1376 LOG_ERROR <<
"Invalid signature of the manifest reported by Secondary: " 1377 <<
" serial: " << pending_ecu.first <<
" manifest: " << manifest;
1381 auto current_ecu_hash = manifest.installedImageHash();
1382 if (pending_ecu.second == current_ecu_hash) {
1383 LOG_INFO <<
"The pending update " << current_ecu_hash <<
" has been installed on " << pending_ecu.first;
1384 boost::optional<Uptane::Target> pending_version;
1385 if (storage->loadInstalledVersions(pending_ecu.first.ToString(),
nullptr, &pending_version)) {
1386 storage->saveEcuInstallationResult(pending_ecu.first,
1389 storage->saveInstalledVersion(pending_ecu.first.ToString(), *pending_version,
1390 InstalledVersionUpdateMode::kCurrent);
1392 report_queue->enqueue(std_::make_unique<EcuInstallationCompletedReport>(
1393 pending_ecu.first, pending_version->correlation_id(),
true));
1396 std::string raw_report;
1397 computeDeviceInstallationResult(&ir, &raw_report);
1398 storage->storeDeviceInstallationResult(ir, raw_report, pending_version->correlation_id());
1404 boost::optional<Uptane::HardwareIdentifier> SotaUptaneClient::getEcuHwId(
const Uptane::EcuSerial &serial)
const {
1405 if (serial == primary_ecu_serial_ || serial.ToString().empty()) {
1406 if (primary_ecu_hw_id_ == Uptane::HardwareIdentifier::Unknown()) {
1409 return primary_ecu_hw_id_;
1412 const auto it = secondaries.find(serial);
1413 if (it != secondaries.end()) {
1414 return it->second->getHwId();
Provides a thread-safe way to pause and terminate task execution.
Container for information about downloading an update.
bool canContinue(bool blocking=true) const
Called by the controlled thread to query the currently requested state.
Metadata version numbers.
Container for information about available campaigns.
bool IsOstree() const
Is this an OSTree target? OSTree targets need special treatment because the hash doesn't represent th...
UpdateStatus
Status of an update.
Operation has already been processed.
Package installation failed.
Metadata verification failed.
Container for information about installing an update.
SWM Internal integrity error.
The Hash class The hash of a file or Uptane metadata.
Container for information about available updates.
Results of libaktualizr API calls.