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) {
30 if (storage->loadEcuRegistered()) {
32 storage->loadEcuSerials(&serials);
34 if (std::find_if(serials.cbegin(), serials.cend(), secondary_comp) == serials.cend()) {
35 throw std::logic_error(
"Adding new Secondaries to a provisioned device is not implemented yet");
39 const auto map_it = secondaries.find(serial);
40 if (map_it != secondaries.end()) {
41 throw std::runtime_error(std::string(
"Multiple Secondaries found with the same serial: ") + serial.ToString());
44 secondaries.emplace(serial, sec);
47 std::vector<Uptane::Target> SotaUptaneClient::findForEcu(
const std::vector<Uptane::Target> &targets,
49 std::vector<Uptane::Target>
result;
50 for (
auto it = targets.begin(); it != targets.end(); ++it) {
51 if (it->ecus().find(ecu_id) != it->ecus().end()) {
52 result.push_back(*it);
59 LOG_INFO <<
"Installing package using " << package_manager_->name() <<
" package manager";
61 return package_manager_->install(target);
62 }
catch (std::exception &ex) {
63 LOG_ERROR <<
"Installation failed: " << ex.what();
68 void SotaUptaneClient::finalizeAfterReboot() {
71 if (!hasPendingUpdates()) {
72 LOG_DEBUG <<
"No pending updates, continuing with initialization";
76 LOG_INFO <<
"Checking for a pending update to apply for Primary ECU";
79 boost::optional<Uptane::Target> pending_target;
80 storage->loadInstalledVersions(primary_ecu_serial.ToString(),
nullptr, &pending_target);
82 if (!pending_target) {
83 LOG_ERROR <<
"No pending update for Primary ECU found, continuing with initialization";
87 LOG_INFO <<
"Pending update for Primary ECU was found, trying to apply it...";
91 if (install_res.result_code == data::ResultCode::Numeric::kNeedCompletion) {
92 LOG_INFO <<
"Pending update for Primary ECU was not applied because reboot was not detected, " 93 "continuing with initialization";
97 storage->saveEcuInstallationResult(primary_ecu_serial, install_res);
99 const std::string correlation_id = pending_target->correlation_id();
100 if (install_res.success) {
101 storage->saveInstalledVersion(primary_ecu_serial.ToString(), *pending_target, InstalledVersionUpdateMode::kCurrent);
103 report_queue->enqueue(std_::make_unique<EcuInstallationCompletedReport>(primary_ecu_serial, correlation_id,
true));
106 storage->saveInstalledVersion(primary_ecu_serial.ToString(), *pending_target, InstalledVersionUpdateMode::kNone);
107 report_queue->enqueue(std_::make_unique<EcuInstallationCompletedReport>(primary_ecu_serial, correlation_id,
false));
110 director_repo.dropTargets(*storage);
113 std::string raw_report;
114 computeDeviceInstallationResult(&ir, &raw_report);
115 storage->storeDeviceInstallationResult(ir, raw_report, correlation_id);
131 storage->saveInstalledVersion(ecu_serial.ToString(), target, InstalledVersionUpdateMode::kNone);
133 result = PackageInstall(target);
134 if (result.result_code.num_code == data::ResultCode::Numeric::kOk) {
136 storage->saveInstalledVersion(ecu_serial.ToString(), target, InstalledVersionUpdateMode::kCurrent);
137 }
else if (result.result_code.num_code == data::ResultCode::Numeric::kNeedCompletion) {
139 storage->saveInstalledVersion(ecu_serial.ToString(), target, InstalledVersionUpdateMode::kPending);
141 storage->saveEcuInstallationResult(ecu_serial, result);
150 void SotaUptaneClient::reportHwInfo(
const Json::Value &custom_hwinfo) {
151 Json::Value system_info;
152 std::string stored_hash;
153 storage->loadDeviceDataHash(
"hardware_info", &stored_hash);
155 if (custom_hwinfo.empty()) {
156 if (!stored_hash.empty()) {
157 LOG_TRACE <<
"Not reporting default hardware information because it has already been reported";
160 system_info = Utils::getHardwareInfo();
161 if (system_info.empty()) {
162 LOG_WARNING <<
"Unable to fetch hardware information from host system.";
167 const Json::Value &hw_info = custom_hwinfo.empty() ? system_info : custom_hwinfo;
168 const Hash new_hash = Hash::generate(Hash::Type::kSha256, Utils::jsonToCanonicalStr(hw_info));
169 if (new_hash !=
Hash(Hash::Type::kSha256, stored_hash)) {
170 if (custom_hwinfo.empty()) {
171 LOG_DEBUG <<
"Reporting default hardware information";
173 LOG_DEBUG <<
"Reporting custom hardware information";
175 const HttpResponse response = http->put(config.tls.server +
"/system_info", hw_info);
176 if (response.isOk()) {
177 storage->storeDeviceDataHash(
"hardware_info", new_hash.HashString());
180 LOG_TRACE <<
"Not reporting hardware information because it has not changed";
184 void SotaUptaneClient::reportInstalledPackages() {
185 const Json::Value packages = package_manager_->getInstalledPackages();
186 const Hash new_hash = Hash::generate(Hash::Type::kSha256, Utils::jsonToCanonicalStr(packages));
187 std::string stored_hash;
188 if (!(storage->loadDeviceDataHash(
"installed_packages", &stored_hash) &&
189 new_hash ==
Hash(Hash::Type::kSha256, stored_hash))) {
190 LOG_DEBUG <<
"Reporting installed packages";
191 const HttpResponse response = http->put(config.tls.server +
"/core/installed", packages);
192 if (response.isOk()) {
193 storage->storeDeviceDataHash(
"installed_packages", new_hash.HashString());
196 LOG_TRACE <<
"Not reporting installed packages because they have not changed";
200 void SotaUptaneClient::reportNetworkInfo() {
202 LOG_TRACE <<
"Not reporting network information because telemetry is disabled";
206 const Json::Value network_info = Utils::getNetworkInfo();
207 const Hash new_hash = Hash::generate(Hash::Type::kSha256, Utils::jsonToCanonicalStr(network_info));
208 std::string stored_hash;
209 if (!(storage->loadDeviceDataHash(
"network_info", &stored_hash) &&
210 new_hash ==
Hash(Hash::Type::kSha256, stored_hash))) {
211 LOG_DEBUG <<
"Reporting network information";
212 const HttpResponse response = http->put(config.tls.server +
"/system_info/network", network_info);
213 if (response.isOk()) {
214 storage->storeDeviceDataHash(
"network_info", new_hash.HashString());
217 LOG_TRACE <<
"Not reporting network information because it has not changed";
221 void SotaUptaneClient::reportAktualizrConfiguration() {
222 if (!config.telemetry.report_config) {
223 LOG_TRACE <<
"Not reporting libaktualizr configuration because telemetry is disabled";
227 std::stringstream conf_ss;
228 config.writeToStream(conf_ss);
229 const std::string conf_str = conf_ss.str();
230 const Hash new_hash = Hash::generate(Hash::Type::kSha256, conf_str);
231 std::string stored_hash;
232 if (!(storage->loadDeviceDataHash(
"configuration", &stored_hash) &&
233 new_hash ==
Hash(Hash::Type::kSha256, stored_hash))) {
234 LOG_DEBUG <<
"Reporting libaktualizr configuration";
235 const HttpResponse response = http->post(config.tls.server +
"/system_info/config",
"application/toml", conf_str);
236 if (response.isOk()) {
237 storage->storeDeviceDataHash(
"configuration", new_hash.HashString());
240 LOG_TRACE <<
"Not reporting libaktualizr configuration because it has not changed";
244 Json::Value SotaUptaneClient::AssembleManifest() {
245 Json::Value manifest;
247 manifest[
"primary_ecu_serial"] = primary_ecu_serial.ToString();
250 Json::Value version_manifest;
252 Json::Value primary_manifest = uptane_manifest->assembleManifest(package_manager_->getCurrent());
253 std::vector<std::pair<Uptane::EcuSerial, int64_t>> ecu_cnt;
254 std::string report_counter;
255 if (!storage->loadEcuReportCounter(&ecu_cnt) || (ecu_cnt.size() == 0)) {
256 LOG_ERROR <<
"No ECU version report counter, please check the database!";
259 report_counter = std::to_string(ecu_cnt[0].second + 1);
260 storage->saveEcuReportCounter(ecu_cnt[0].first, ecu_cnt[0].second + 1);
262 version_manifest[primary_ecu_serial.ToString()] = uptane_manifest->sign(primary_manifest, report_counter);
264 for (
auto it = secondaries.begin(); it != secondaries.end(); it++) {
268 bool from_cache =
false;
269 if (secmanifest == Json::Value()) {
272 if (storage->loadCachedEcuManifest(ecu_serial, &cached)) {
273 LOG_WARNING <<
"Could not reach Secondary " << ecu_serial <<
", sending a cached version of its manifest";
274 secmanifest = Utils::parseJSON(cached);
277 LOG_ERROR <<
"Could not fetch a valid Secondary manifest from cache";
281 if (secmanifest.verifySignature(it->second->getPublicKey())) {
282 version_manifest[ecu_serial.ToString()] = secmanifest;
284 storage->storeCachedEcuManifest(ecu_serial, Utils::jsonToCanonicalStr(secmanifest));
288 LOG_ERROR <<
"Secondary manifest is corrupted or not signed, or signature is invalid manifest: " << secmanifest;
291 manifest[
"ecu_version_manifests"] = version_manifest;
294 Json::Value installation_report;
297 std::string raw_report;
298 std::string correlation_id;
299 bool has_results = storage->loadDeviceInstallationResult(&dev_result, &raw_report, &correlation_id);
301 if (!(dev_result.isSuccess() || dev_result.needCompletion())) {
302 director_repo.dropTargets(*storage);
305 installation_report[
"result"] = dev_result.toJson();
306 installation_report[
"raw_report"] = raw_report;
307 installation_report[
"correlation_id"] = correlation_id;
308 installation_report[
"items"] = Json::arrayValue;
310 std::vector<std::pair<Uptane::EcuSerial, data::InstallationResult>> ecu_results;
311 storage->loadEcuInstallationResults(&ecu_results);
312 for (
const auto &r : ecu_results) {
317 item[
"ecu"] = serial.ToString();
318 item[
"result"] = res.toJson();
320 installation_report[
"items"].append(item);
323 manifest[
"installation_report"][
"content_type"] =
"application/vnd.com.here.otac.installationReport.v1";
324 manifest[
"installation_report"][
"report"] = installation_report;
326 LOG_DEBUG <<
"No installation result to report in manifest";
332 bool SotaUptaneClient::hasPendingUpdates()
const {
return storage->hasPendingInstall(); }
334 void SotaUptaneClient::initialize() {
335 LOG_DEBUG <<
"Checking if device is provisioned...";
336 auto keys = std::make_shared<KeyManager>(storage, config.keymanagerConfig());
338 Initializer initializer(config.provision, storage, http, *keys, secondaries);
342 if (!storage->loadEcuSerials(&serials) || serials.size() == 0) {
343 throw std::runtime_error(
"Unable to load ECU serials after device registration.");
346 uptane_manifest = std::make_shared<Uptane::ManifestIssuer>(keys, serials[0].first);
347 primary_ecu_serial_ = serials[0].first;
348 primary_ecu_hw_id_ = serials[0].second;
349 LOG_INFO <<
"Primary ECU serial: " << primary_ecu_serial_ <<
" with hardware ID: " << primary_ecu_hw_id_;
353 std::string device_id;
354 if (!storage->loadDeviceId(&device_id)) {
355 throw std::runtime_error(
"Unable to load device ID after device registration.");
357 LOG_INFO <<
"Device ID: " << device_id;
358 LOG_INFO <<
"Device Gateway URL: " << config.tls.server;
362 std::string not_before;
363 std::string not_after;
364 keys->getCertInfo(&subject, &issuer, ¬_before, ¬_after);
365 LOG_INFO <<
"Certificate subject: " << subject;
366 LOG_INFO <<
"Certificate issuer: " << issuer;
367 LOG_INFO <<
"Certificate valid from: " << not_before <<
" until: " << not_after;
369 LOG_DEBUG <<
"... provisioned OK";
370 finalizeAfterReboot();
373 void SotaUptaneClient::updateDirectorMeta() {
375 director_repo.updateMeta(*storage, *uptane_fetcher);
376 }
catch (
const std::exception &e) {
377 LOG_ERROR <<
"Director metadata update failed: " << e.what();
382 void SotaUptaneClient::updateImageMeta() {
384 image_repo.updateMeta(*storage, *uptane_fetcher);
385 }
catch (
const std::exception &e) {
386 LOG_ERROR <<
"Failed to update Image repo metadata: " << e.what();
391 void SotaUptaneClient::checkDirectorMetaOffline() {
393 director_repo.checkMetaOffline(*storage);
394 }
catch (
const std::exception &e) {
395 LOG_ERROR <<
"Failed to check Director metadata: " << e.what();
400 void SotaUptaneClient::checkImageMetaOffline() {
402 image_repo.checkMetaOffline(*storage);
403 }
catch (
const std::exception &e) {
404 LOG_ERROR <<
"Failed to check Image repo metadata: " << e.what();
409 std::string *raw_installation_report)
const {
412 std::string raw_ir =
"Installation succesful";
415 std::vector<std::pair<Uptane::EcuSerial, data::InstallationResult>> ecu_results;
417 if (!storage->loadEcuInstallationResults(&ecu_results)) {
420 "Unable to get installation results from ecus");
421 raw_ir =
"Failed to load ECUs' installation result";
426 std::string result_code_err_str;
428 for (
const auto &r : ecu_results) {
429 auto ecu_serial = r.first;
430 auto installation_res = r.second;
432 auto hw_id = ecuHwId(ecu_serial);
437 "Unable to get installation results from ECUs");
439 raw_ir =
"Couldn't find any ECU with the given serial: " + ecu_serial.ToString();
444 if (installation_res.needCompletion()) {
446 device_installation_result =
448 "ECU needs completion/finalization to be installed: " + ecu_serial.ToString());
449 raw_ir =
"ECU needs completion/finalization to be installed: " + ecu_serial.ToString();
456 if (!installation_res.isSuccess()) {
457 std::string ecu_code_str = (*hw_id).ToString() +
":" + installation_res.result_code.toString();
458 result_code_err_str += (result_code_err_str !=
"" ?
"|" :
"") + ecu_code_str;
462 if (!result_code_err_str.empty()) {
464 device_installation_result =
466 "Installation failed on at least one of ECUs");
467 raw_ir =
"Installation failed on at least one of ECUs";
474 if (result !=
nullptr) {
475 *result = device_installation_result;
478 if (raw_installation_report !=
nullptr) {
479 *raw_installation_report = raw_ir;
483 void SotaUptaneClient::getNewTargets(std::vector<Uptane::Target> *new_targets,
unsigned int *ecus_count) {
484 const std::vector<Uptane::Target> targets = director_repo.getTargets().targets;
486 if (ecus_count !=
nullptr) {
491 for (
const auto &ecu : target.ecus()) {
499 const auto hw_id_known = ecuHwId(ecu_serial);
501 LOG_ERROR <<
"Unknown ECU ID in Director Targets metadata: " << ecu_serial.ToString();
505 if (*hw_id_known != hw_id) {
506 LOG_ERROR <<
"Wrong hardware identifier for ECU " << ecu_serial.ToString();
510 boost::optional<Uptane::Target> current_version;
511 if (!storage->loadInstalledVersions(ecu_serial.ToString(), ¤t_version,
nullptr)) {
512 LOG_WARNING <<
"Could not load currently installed version for ECU ID: " << ecu_serial.ToString();
516 if (!current_version) {
517 LOG_WARNING <<
"Current version for ECU ID: " << ecu_serial.ToString() <<
" is unknown";
519 }
else if (current_version->MatchTarget(target)) {
521 }
else if (current_version->filename() == target.filename()) {
522 LOG_ERROR <<
"Director Target filename matches currently installed version, but content differs!";
531 if (primary_ecu_serial == ecu_serial) {
533 (config.pacman.type == PACKAGE_MANAGER_OSTREE || config.pacman.type == PACKAGE_MANAGER_OSTREEDOCKERAPP)) {
534 LOG_ERROR <<
"Cannot install a non-OSTree package on an OSTree system";
539 if (is_new && ecus_count !=
nullptr) {
545 new_targets->push_back(target);
550 std::unique_ptr<Uptane::Target> SotaUptaneClient::findTargetHelper(
const Uptane::Targets &cur_targets,
552 const int level,
const bool terminating,
553 const bool offline) {
555 const auto it = std::find_if(cur_targets.targets.cbegin(), cur_targets.targets.cend(), target_comp);
556 if (it != cur_targets.targets.cend()) {
557 return std_::make_unique<Uptane::Target>(*it);
560 if (terminating || level >= Uptane::kDelegationsMaxDepth) {
561 return std::unique_ptr<Uptane::Target>(
nullptr);
564 for (
const auto &delegate_name : cur_targets.delegated_role_names_) {
565 Uptane::Role delegate_role = Uptane::Role::Delegation(delegate_name);
566 auto patterns = cur_targets.paths_for_role_.find(delegate_role);
567 if (patterns == cur_targets.paths_for_role_.end()) {
572 for (
const auto &pattern : patterns->second) {
573 if (fnmatch(pattern.c_str(), queried_target.filename().c_str(), 0) == 0) {
585 Uptane::getTrustedDelegation(delegate_role, cur_targets, image_repo, *storage, *uptane_fetcher, offline);
586 if (delegation.isExpired(TimeStamp::Now())) {
590 auto is_terminating = cur_targets.terminating_role_.find(delegate_role);
591 if (is_terminating == cur_targets.terminating_role_.end()) {
595 auto found_target = findTargetHelper(delegation, queried_target, level + 1, is_terminating->second, offline);
596 if (found_target !=
nullptr) {
601 return std::unique_ptr<Uptane::Target>(
nullptr);
604 std::unique_ptr<Uptane::Target> SotaUptaneClient::findTargetInDelegationTree(
const Uptane::Target &target,
605 const bool offline) {
606 auto toplevel_targets = image_repo.getTargets();
607 if (toplevel_targets ==
nullptr) {
608 return std::unique_ptr<Uptane::Target>(
nullptr);
611 return findTargetHelper(*toplevel_targets, target, 0,
false, offline);
614 result::Download SotaUptaneClient::downloadImages(
const std::vector<Uptane::Target> &targets,
618 std::lock_guard<std::mutex> guard(download_mutex);
620 std::vector<Uptane::Target> downloaded_targets;
624 update_status = checkUpdatesOffline(targets);
625 }
catch (
const std::exception &e) {
626 last_exception = std::current_exception();
627 update_status = result::UpdateStatus::kError;
630 if (update_status == result::UpdateStatus::kNoUpdatesAvailable) {
631 result =
result::Download({}, result::DownloadStatus::kNothingToDownload,
"");
632 }
else if (update_status == result::UpdateStatus::kError) {
633 result =
result::Download(downloaded_targets, result::DownloadStatus::kError,
"Error rechecking stored metadata.");
634 storeInstallationFailure(
638 if (update_status != result::UpdateStatus::kUpdatesAvailable) {
639 sendEvent<event::AllDownloadsComplete>(result);
643 for (
const auto &target : targets) {
644 auto res = downloadImage(target, token);
646 downloaded_targets.push_back(res.second);
650 if (targets.size() == downloaded_targets.size()) {
651 result =
result::Download(downloaded_targets, result::DownloadStatus::kSuccess,
"");
653 if (downloaded_targets.size() == 0) {
654 LOG_ERROR <<
"0 of " << targets.size() <<
" targets were successfully downloaded.";
655 result =
result::Download(downloaded_targets, result::DownloadStatus::kError,
"Each target download has failed");
657 LOG_ERROR <<
"Only " << downloaded_targets.size() <<
" of " << targets.size() <<
" were successfully downloaded.";
658 result =
result::Download(downloaded_targets, result::DownloadStatus::kPartialSuccess,
"");
660 storeInstallationFailure(
664 sendEvent<event::AllDownloadsComplete>(result);
668 void SotaUptaneClient::reportPause() {
669 const std::string &correlation_id = director_repo.getCorrelationId();
670 report_queue->enqueue(std_::make_unique<DevicePausedReport>(correlation_id));
673 void SotaUptaneClient::reportResume() {
674 const std::string &correlation_id = director_repo.getCorrelationId();
675 report_queue->enqueue(std_::make_unique<DeviceResumedReport>(correlation_id));
678 std::pair<bool, Uptane::Target> SotaUptaneClient::downloadImage(
const Uptane::Target &target,
682 const std::string &correlation_id = director_repo.getCorrelationId();
684 for (
const auto &ecu : target.ecus()) {
685 report_queue->enqueue(std_::make_unique<EcuDownloadStartedReport>(ecu.first, correlation_id));
692 bool success =
false;
694 KeyManager keys(storage, config.keymanagerConfig());
696 auto prog_cb = [
this](
const Uptane::Target &t,
const std::string &description,
unsigned int progress) {
697 report_progress_cb(events_channel.get(), t, description, progress);
702 if (target.IsForEcu(primary_ecu_serial) || !target.
IsOstree()) {
704 const int max_tries = 3;
706 std::chrono::milliseconds wait(500);
708 for (; tries < max_tries; tries++) {
709 success = package_manager_->fetchTarget(target, *uptane_fetcher, keys, prog_cb, token);
712 if (success || (token !=
nullptr && !token->
canContinue(
false))) {
714 }
else if (tries < max_tries - 1) {
715 std::this_thread::sleep_for(wait);
720 LOG_ERROR <<
"Download unsuccessful after " << tries <<
" attempts.";
728 }
catch (
const std::exception &e) {
729 LOG_ERROR <<
"Error downloading image: " << e.what();
730 last_exception = std::current_exception();
735 for (
const auto &ecu : target.ecus()) {
736 report_queue->enqueue(std_::make_unique<EcuDownloadCompletedReport>(ecu.first, correlation_id, success));
739 sendEvent<event::DownloadTargetComplete>(target, success);
740 return {success, target};
743 void SotaUptaneClient::uptaneIteration(std::vector<Uptane::Target> *targets,
unsigned int *ecus_count) {
744 updateDirectorMeta();
746 std::vector<Uptane::Target> tmp_targets;
749 getNewTargets(&tmp_targets, &ecus);
750 }
catch (
const std::exception &e) {
751 LOG_ERROR <<
"Inconsistency between Director metadata and available ECUs: " << e.what();
755 if (!tmp_targets.empty()) {
756 LOG_INFO <<
"New updates found in Director metadata. Checking Image repo metadata...";
760 if (targets !=
nullptr) {
761 *targets = std::move(tmp_targets);
763 if (ecus_count !=
nullptr) {
768 void SotaUptaneClient::uptaneOfflineIteration(std::vector<Uptane::Target> *targets,
unsigned int *ecus_count) {
769 checkDirectorMetaOffline();
771 std::vector<Uptane::Target> tmp_targets;
774 getNewTargets(&tmp_targets, &ecus);
775 }
catch (
const std::exception &e) {
776 LOG_ERROR <<
"Inconsistency between Director metadata and available ECUs: " << e.what();
780 if (!tmp_targets.empty()) {
781 LOG_DEBUG <<
"New updates found in stored Director metadata. Checking stored Image repo metadata...";
782 checkImageMetaOffline();
785 if (targets !=
nullptr) {
786 *targets = std::move(tmp_targets);
788 if (ecus_count !=
nullptr) {
793 void SotaUptaneClient::sendDeviceData(
const Json::Value &custom_hwinfo) {
794 reportHwInfo(custom_hwinfo);
795 reportInstalledPackages();
797 reportAktualizrConfiguration();
798 sendEvent<event::SendDeviceDataComplete>();
806 if (hasPendingUpdates()) {
808 LOG_INFO <<
"The current update is pending. Check if pending ECUs has been already updated";
809 checkAndUpdatePendingSecondaries();
812 if (hasPendingUpdates()) {
815 LOG_INFO <<
"An update is pending. Skipping check for update until installation is complete.";
817 "There are pending updates, no new updates are checked");
821 if (!putManifestSimple()) {
822 LOG_ERROR <<
"Error sending manifest!";
824 result = checkUpdates();
825 sendEvent<event::UpdateCheckComplete>(result);
833 std::vector<Uptane::Target> updates;
834 unsigned int ecus_count = 0;
836 uptaneIteration(&updates, &ecus_count);
837 }
catch (
const std::exception &e) {
838 last_exception = std::current_exception();
839 result =
result::UpdateCheck({}, 0, result::UpdateStatus::kError, Json::nullValue,
"Could not update metadata.");
843 std::string director_targets;
844 if (!storage->loadNonRoot(&director_targets, Uptane::RepositoryType::Director(), Uptane::Role::Targets())) {
846 throw std::runtime_error(
"Could not get Director's Targets from storage");
849 if (updates.empty()) {
850 LOG_DEBUG <<
"No new updates found in Uptane metadata.";
852 result::UpdateCheck({}, 0, result::UpdateStatus::kNoUpdatesAvailable, Utils::parseJSON(director_targets),
"");
861 for (
auto &target : updates) {
862 auto image_target = findTargetInDelegationTree(target,
false);
863 if (image_target ==
nullptr) {
865 LOG_ERROR <<
"No matching target in Image repo Targets metadata for " << target;
870 if (target.uri().empty() && !image_target->uri().empty()) {
871 target.setUri(image_target->uri());
874 }
catch (
const std::exception &e) {
875 last_exception = std::current_exception();
876 result =
result::UpdateCheck({}, 0, result::UpdateStatus::kError, Utils::parseJSON(director_targets),
878 storeInstallationFailure(
884 Utils::parseJSON(director_targets),
"");
885 if (updates.size() == 1) {
886 LOG_INFO <<
"1 new update found in both Director and Image repo metadata.";
888 LOG_INFO << updates.size() <<
" new updates found in both Director and Image repo metadata.";
893 result::UpdateStatus SotaUptaneClient::checkUpdatesOffline(
const std::vector<Uptane::Target> &targets) {
894 if (hasPendingUpdates()) {
896 LOG_INFO <<
"An update is pending. Skipping stored metadata check until installation is complete.";
897 return result::UpdateStatus::kError;
900 if (targets.empty()) {
901 LOG_WARNING <<
"Requested targets vector is empty. Nothing to do.";
902 return result::UpdateStatus::kError;
905 std::vector<Uptane::Target> director_targets;
906 unsigned int ecus_count = 0;
908 uptaneOfflineIteration(&director_targets, &ecus_count);
909 }
catch (
const std::exception &e) {
910 LOG_ERROR <<
"Aborting; invalid Uptane metadata in storage.";
914 if (director_targets.empty()) {
915 LOG_ERROR <<
"No new updates found while rechecking stored Director Targets metadata, but " << targets.size()
916 <<
" target(s) were requested.";
917 return result::UpdateStatus::kNoUpdatesAvailable;
923 for (
const auto &target : targets) {
925 const auto it = std::find_if(director_targets.cbegin(), director_targets.cend(), target_comp);
926 if (it == director_targets.cend()) {
927 LOG_ERROR <<
"No matching target in Director Targets metadata for " << target;
928 throw Uptane::Exception(Uptane::RepositoryType::DIRECTOR,
"No matching target in Director Targets metadata");
931 const auto image_target = findTargetInDelegationTree(target,
true);
932 if (image_target ==
nullptr) {
933 LOG_ERROR <<
"No matching target in Image repo Targets metadata for " << target;
934 throw Uptane::Exception(Uptane::RepositoryType::IMAGE,
"No matching target in Director Targets metadata");
938 return result::UpdateStatus::kUpdatesAvailable;
941 result::Install SotaUptaneClient::uptaneInstall(
const std::vector<Uptane::Target> &updates) {
942 const std::string &correlation_id = director_repo.getCorrelationId();
947 std::string raw_report;
949 std::tie(r, raw_report) = [
this, &updates, &correlation_id]() -> std::tuple<result::Install, std::string> {
956 update_status = checkUpdatesOffline(updates);
957 }
catch (
const std::exception &e) {
958 last_exception = std::current_exception();
959 update_status = result::UpdateStatus::kError;
962 if (update_status != result::UpdateStatus::kUpdatesAvailable) {
963 if (update_status == result::UpdateStatus::kNoUpdatesAvailable) {
968 return std::make_tuple(result,
"Stored Uptane metadata is invalid");
973 for (
const auto &update : updates) {
974 if (update.IsForEcu(primary_ecu_serial) || !update.IsOstree()) {
980 if (package_manager_->verifyTarget(update) != TargetStatus::kGood) {
982 return std::make_tuple(result,
"Downloaded target is invalid");
991 if (!waitSecondariesReachable(updates)) {
993 return std::make_tuple(result,
"Secondaries were not available");
997 std::vector<Uptane::Target> primary_updates = findForEcu(updates, primary_ecu_serial);
1000 if (!sendMetadataToEcus(updates)) {
1002 return std::make_tuple(result,
"Secondary metadata verification failed");
1006 if (primary_updates.size() != 0u) {
1009 primary_update.setCorrelationId(correlation_id);
1011 report_queue->enqueue(std_::make_unique<EcuInstallationStartedReport>(primary_ecu_serial, correlation_id));
1012 sendEvent<event::InstallStarted>(primary_ecu_serial);
1017 package_manager_->updateNotify();
1018 install_res = PackageInstallSetResult(primary_update);
1019 if (install_res.result_code.num_code == data::ResultCode::Numeric::kNeedCompletion) {
1021 report_queue->enqueue(std_::make_unique<EcuInstallationAppliedReport>(primary_ecu_serial, correlation_id));
1022 sendEvent<event::InstallTargetComplete>(primary_ecu_serial,
true);
1023 }
else if (install_res.result_code.num_code == data::ResultCode::Numeric::kOk) {
1024 storage->saveEcuInstallationResult(primary_ecu_serial, install_res);
1025 report_queue->enqueue(
1026 std_::make_unique<EcuInstallationCompletedReport>(primary_ecu_serial, correlation_id,
true));
1027 sendEvent<event::InstallTargetComplete>(primary_ecu_serial,
true);
1030 storage->saveEcuInstallationResult(primary_ecu_serial, install_res);
1031 report_queue->enqueue(
1032 std_::make_unique<EcuInstallationCompletedReport>(primary_ecu_serial, correlation_id,
false));
1033 sendEvent<event::InstallTargetComplete>(primary_ecu_serial,
false);
1035 result.ecu_reports.emplace(result.ecu_reports.begin(), primary_update, primary_ecu_serial, install_res);
1037 LOG_INFO <<
"No update to install on Primary";
1040 auto sec_reports = sendImagesToEcus(updates);
1041 result.ecu_reports.insert(result.ecu_reports.end(), sec_reports.begin(), sec_reports.end());
1043 computeDeviceInstallationResult(&result.dev_report, &rr);
1045 return std::make_tuple(result, rr);
1049 storage->storeDeviceInstallationResult(r.dev_report, raw_report, correlation_id);
1051 sendEvent<event::AllInstallsComplete>(r);
1057 auto campaigns = campaign::Campaign::fetchAvailableCampaigns(*http, config.tls.server);
1058 for (
const auto &c : campaigns) {
1059 LOG_INFO <<
"Campaign: " << c.name;
1060 LOG_INFO <<
"Campaign id: " << c.id;
1061 LOG_INFO <<
"Campaign size: " << c.size;
1062 LOG_INFO <<
"CampaignAccept required: " << (c.autoAccept ?
"no" :
"yes");
1063 LOG_INFO <<
"Message: " << c.description;
1066 sendEvent<event::CampaignCheckComplete>(result);
1070 void SotaUptaneClient::campaignAccept(
const std::string &campaign_id) {
1071 sendEvent<event::CampaignAcceptComplete>();
1072 report_queue->enqueue(std_::make_unique<CampaignAcceptedReport>(campaign_id));
1075 void SotaUptaneClient::campaignDecline(
const std::string &campaign_id) {
1076 sendEvent<event::CampaignDeclineComplete>();
1077 report_queue->enqueue(std_::make_unique<CampaignDeclinedReport>(campaign_id));
1080 void SotaUptaneClient::campaignPostpone(
const std::string &campaign_id) {
1081 sendEvent<event::CampaignPostponeComplete>();
1082 report_queue->enqueue(std_::make_unique<CampaignPostponedReport>(campaign_id));
1085 bool SotaUptaneClient::isInstallCompletionRequired()
const {
1086 std::vector<std::pair<Uptane::EcuSerial, Hash>> pending_ecus;
1087 storage->getPendingEcus(&pending_ecus);
1088 bool pending_for_ecu = std::find_if(pending_ecus.begin(), pending_ecus.end(),
1089 [
this](
const std::pair<Uptane::EcuSerial, Hash> &ecu) ->
bool {
1090 return ecu.first == primary_ecu_serial_;
1091 }) != pending_ecus.end();
1093 return pending_for_ecu && config.uptane.force_install_completion;
1096 void SotaUptaneClient::completeInstall()
const {
1097 if (isInstallCompletionRequired()) {
1098 package_manager_->completeInstall();
1102 bool SotaUptaneClient::putManifestSimple(
const Json::Value &custom) {
1104 if (hasPendingUpdates()) {
1107 LOG_DEBUG <<
"An update is pending. Skipping manifest upload until installation is complete.";
1111 static bool connected =
true;
1112 auto manifest = AssembleManifest();
1113 if (custom != Json::nullValue) {
1114 manifest[
"custom"] = custom;
1116 auto signed_manifest = uptane_manifest->sign(manifest);
1117 HttpResponse response = http->put(config.uptane.director_server +
"/manifest", signed_manifest);
1118 if (response.isOk()) {
1120 LOG_INFO <<
"Connectivity is restored.";
1123 storage->clearInstallationResults();
1130 LOG_WARNING <<
"Put manifest request failed: " << response.getStatusStr();
1134 bool SotaUptaneClient::putManifest(
const Json::Value &custom) {
1135 bool success = putManifestSimple(custom);
1136 sendEvent<event::PutManifestComplete>(success);
1141 void SotaUptaneClient::verifySecondaries() {
1142 storage->clearMisconfiguredEcus();
1144 if (!storage->loadEcuSerials(&serials) || serials.empty()) {
1145 LOG_ERROR <<
"No ECU serials found in storage!";
1149 std::vector<MisconfiguredEcu> misconfigured_ecus;
1150 std::vector<bool> found(serials.size(),
false);
1152 EcuSerials::const_iterator store_it;
1153 store_it = std::find_if(serials.cbegin(), serials.cend(), primary_comp);
1154 if (store_it == serials.cend()) {
1155 LOG_ERROR <<
"Primary ECU serial " << primaryEcuSerial() <<
" not found in storage!";
1158 found[
static_cast<size_t>(std::distance(serials.cbegin(), store_it))] =
true;
1161 for (
auto it = secondaries.cbegin(); it != secondaries.cend(); ++it) {
1163 store_it = std::find_if(serials.cbegin(), serials.cend(), secondary_comp);
1164 if (store_it == serials.cend()) {
1165 LOG_ERROR <<
"Secondary ECU serial " << it->second->getSerial() <<
" (hardware ID " << it->second->getHwId()
1166 <<
") not found in storage!";
1167 misconfigured_ecus.emplace_back(it->second->getSerial(), it->second->getHwId(), EcuState::kNotRegistered);
1168 }
else if (found[static_cast<size_t>(std::distance(serials.cbegin(), store_it))]) {
1169 LOG_ERROR <<
"Secondary ECU serial " << it->second->getSerial() <<
" (hardware ID " << it->second->getHwId()
1170 <<
") has a duplicate entry in storage!";
1172 found[
static_cast<size_t>(std::distance(serials.cbegin(), store_it))] =
true;
1176 std::vector<bool>::iterator found_it;
1177 for (found_it = found.begin(); found_it != found.end(); ++found_it) {
1179 auto not_registered = serials[
static_cast<size_t>(std::distance(found.begin(), found_it))];
1180 LOG_WARNING <<
"ECU serial " << not_registered.first <<
" in storage was not reported to aktualizr!";
1181 misconfigured_ecus.emplace_back(not_registered.first, not_registered.second, EcuState::kOld);
1185 storage->storeMisconfiguredEcus(misconfigured_ecus);
1188 bool SotaUptaneClient::waitSecondariesReachable(
const std::vector<Uptane::Target> &updates) {
1189 std::map<Uptane::EcuSerial, Uptane::SecondaryInterface *> targeted_secondaries;
1191 for (
const auto &t : updates) {
1192 for (
const auto &ecu : t.ecus()) {
1193 if (ecu.first == primary_ecu_serial) {
1196 auto f = secondaries.find(ecu.first);
1197 if (f == secondaries.end()) {
1198 LOG_ERROR <<
"Target " << t <<
" has unknown ECU ID";
1202 targeted_secondaries[ecu.first] = f->second.get();
1206 if (targeted_secondaries.empty()) {
1210 LOG_INFO <<
"Waiting for Secondaries to connect to start installation...";
1212 auto deadline = std::chrono::system_clock::now() + std::chrono::seconds(config.uptane.secondary_preinstall_wait_sec);
1213 while (std::chrono::system_clock::now() <= deadline) {
1214 if (targeted_secondaries.empty()) {
1218 for (
auto sec_it = targeted_secondaries.begin(); sec_it != targeted_secondaries.end();) {
1219 if (sec_it->second->ping()) {
1220 sec_it = targeted_secondaries.erase(sec_it);
1225 std::this_thread::sleep_for(std::chrono::seconds(1));
1228 LOG_ERROR <<
"Secondaries did not connect: ";
1230 for (
const auto &sec : targeted_secondaries) {
1231 LOG_ERROR << sec.second->getSerial();
1240 const std::string &correlation_id = director_repo.getCorrelationId();
1241 storage->storeDeviceInstallationResult(result,
"", correlation_id);
1243 director_repo.dropTargets(*storage);
1247 std::string latest_root;
1249 if (!storage->loadLatestRoot(&latest_root, repo)) {
1250 LOG_ERROR <<
"No Root metadata to send";
1254 int last_root_version = Uptane::extractVersionUntrusted(latest_root);
1256 int sec_root_version = secondary.getRootVersion((repo == Uptane::RepositoryType::Director()));
1257 if (sec_root_version >= 0) {
1258 for (
int v = sec_root_version + 1; v <= last_root_version; v++) {
1261 LOG_WARNING <<
"Couldn't find Root metadata in the storage, trying remote repo";
1263 uptane_fetcher->fetchRole(&root, Uptane::kMaxRootSize, repo, Uptane::Role::Root(),
Uptane::Version(v));
1264 }
catch (
const std::exception &e) {
1266 LOG_ERROR <<
"Root metadata could not be fetched, skipping to the next Secondary";
1270 if (!secondary.putRoot(root, repo == Uptane::RepositoryType::Director())) {
1271 LOG_ERROR <<
"Sending metadata to " << secondary.getSerial() <<
" failed";
1278 bool SotaUptaneClient::sendMetadataToEcus(
const std::vector<Uptane::Target> &targets) {
1280 if (!storage->loadLatestRoot(&meta.director_root, Uptane::RepositoryType::Director())) {
1281 LOG_ERROR <<
"No Director Root metadata to send";
1284 if (!storage->loadNonRoot(&meta.director_targets, Uptane::RepositoryType::Director(), Uptane::Role::Targets())) {
1285 LOG_ERROR <<
"No Director Targets metadata to send";
1288 if (!storage->loadLatestRoot(&meta.image_root, Uptane::RepositoryType::Image())) {
1289 LOG_ERROR <<
"No Image repo Root metadata to send";
1292 if (!storage->loadNonRoot(&meta.image_timestamp, Uptane::RepositoryType::Image(), Uptane::Role::Timestamp())) {
1293 LOG_ERROR <<
"No Image repo Timestamp metadata to send";
1296 if (!storage->loadNonRoot(&meta.image_snapshot, Uptane::RepositoryType::Image(), Uptane::Role::Snapshot())) {
1297 LOG_ERROR <<
"No Image repo Snapshot metadata to send";
1300 if (!storage->loadNonRoot(&meta.image_targets, Uptane::RepositoryType::Image(), Uptane::Role::Targets())) {
1301 LOG_ERROR <<
"No Image repo Targets metadata to send";
1305 bool put_meta_succeed =
true;
1306 for (
const auto &target : targets) {
1307 for (
const auto &ecu : target.ecus()) {
1309 auto sec = secondaries.find(ecu_serial);
1310 if (sec == secondaries.end()) {
1315 rotateSecondaryRoot(Uptane::RepositoryType::Director(), *(sec->second));
1316 rotateSecondaryRoot(Uptane::RepositoryType::Image(), *(sec->second));
1317 if (!sec->second->putMetadata(meta)) {
1318 LOG_ERROR <<
"Sending metadata to " << sec->first <<
" failed";
1319 put_meta_succeed =
false;
1324 return put_meta_succeed;
1329 auto f = [
this, &secondary, target]() {
1330 const std::string &correlation_id = director_repo.getCorrelationId();
1332 sendEvent<event::InstallStarted>(secondary.getSerial());
1333 report_queue->enqueue(std_::make_unique<EcuInstallationStartedReport>(secondary.getSerial(), correlation_id));
1335 std::string data_to_send;
1336 bool send_firmware_result =
false;
1340 data_to_send = secondaryTreehubCredentials();
1342 std::stringstream sstr;
1343 sstr << *storage->openTargetFile(target);
1344 data_to_send = sstr.str();
1347 if (!data_to_send.empty()) {
1348 send_firmware_result = secondary.sendFirmware(data_to_send);
1354 if (send_firmware_result) {
1355 result = secondary.install(target.filename());
1358 if (result == data::ResultCode::Numeric::kNeedCompletion) {
1359 report_queue->enqueue(std_::make_unique<EcuInstallationAppliedReport>(secondary.getSerial(), correlation_id));
1361 report_queue->enqueue(std_::make_unique<EcuInstallationCompletedReport>(
1362 secondary.getSerial(), correlation_id, result == data::ResultCode::Numeric::kOk));
1365 sendEvent<event::InstallTargetComplete>(secondary.getSerial(), result == data::ResultCode::Numeric::kOk);
1369 return std::async(std::launch::async, f);
1372 std::vector<result::Install::EcuReport> SotaUptaneClient::sendImagesToEcus(
const std::vector<Uptane::Target> &targets) {
1373 std::vector<result::Install::EcuReport> reports;
1374 std::vector<std::pair<result::Install::EcuReport, std::future<data::ResultCode::Numeric>>> firmwareFutures;
1378 for (
auto targets_it = targets.cbegin(); targets_it != targets.cend(); ++targets_it) {
1379 for (
auto ecus_it = targets_it->ecus().cbegin(); ecus_it != targets_it->ecus().cend(); ++ecus_it) {
1382 if (primary_ecu_serial == ecu_serial) {
1386 auto f = secondaries.find(ecu_serial);
1387 if (f == secondaries.end()) {
1388 LOG_ERROR <<
"Target " << *targets_it <<
" has unknown ECU ID";
1394 sendFirmwareAsync(sec, *targets_it));
1398 for (
auto &f : firmwareFutures) {
1401 fut_result = f.second.get();
1404 if (fiu_fail((std::string(
"secondary_install_") + f.first.serial.ToString()).c_str()) != 0) {
1417 if (fut_result == data::ResultCode::Numeric::kOk || fut_result == data::ResultCode::Numeric::kNeedCompletion) {
1418 f.first.update.setCorrelationId(director_repo.getCorrelationId());
1419 auto update_mode = fut_result == data::ResultCode::Numeric::kOk ? InstalledVersionUpdateMode::kCurrent
1420 : InstalledVersionUpdateMode::kPending;
1421 storage->saveInstalledVersion(f.first.serial.ToString(), f.first.update, update_mode);
1424 storage->saveEcuInstallationResult(f.first.serial, f.first.install_res);
1425 reports.push_back(f.first);
1430 std::string SotaUptaneClient::secondaryTreehubCredentials()
const {
1431 if (config.tls.pkey_source != CryptoSource::kFile || config.tls.cert_source != CryptoSource::kFile ||
1432 config.tls.ca_source != CryptoSource::kFile) {
1433 LOG_ERROR <<
"Cannot send OSTree update to a Secondary when not using file as credential sources";
1439 if (!storage->loadTlsCreds(&ca, &cert, &pkey)) {
1440 LOG_ERROR <<
"Could not load TLS credentials from storage";
1444 std::string treehub_url = config.pacman.ostree_server;
1445 std::map<std::string, std::string> archive_map = {
1446 {
"ca.pem", ca}, {
"client.pem", cert}, {
"pkey.pem", pkey}, {
"server.url", treehub_url}};
1449 std::stringstream as;
1450 Utils::writeArchive(archive_map, as);
1453 }
catch (std::runtime_error &exc) {
1454 LOG_ERROR <<
"Could not create credentials archive: " << exc.what();
1463 void SotaUptaneClient::checkAndUpdatePendingSecondaries() {
1465 std::vector<std::pair<Uptane::EcuSerial, Hash>> pending_ecus;
1466 storage->getPendingEcus(&pending_ecus);
1468 for (
const auto &pending_ecu : pending_ecus) {
1469 if (primaryEcuSerial() == pending_ecu.first) {
1472 auto &sec = secondaries[pending_ecu.first];
1473 const auto &manifest = sec->getManifest();
1474 if (manifest == Json::nullValue) {
1475 LOG_DEBUG <<
"Failed to get a manifest from Secondary: " << sec->getSerial();
1478 if (!manifest.verifySignature(sec->getPublicKey())) {
1479 LOG_ERROR <<
"Invalid signature of the manifest reported by Secondary: " 1480 <<
" serial: " << pending_ecu.first <<
" manifest: " << manifest;
1484 auto current_ecu_hash = manifest.installedImageHash();
1485 if (pending_ecu.second == current_ecu_hash) {
1486 LOG_INFO <<
"The pending update " << current_ecu_hash <<
" has been installed on " << pending_ecu.first;
1487 boost::optional<Uptane::Target> pending_version;
1488 if (storage->loadInstalledVersions(pending_ecu.first.ToString(),
nullptr, &pending_version)) {
1489 storage->saveEcuInstallationResult(pending_ecu.first,
1492 storage->saveInstalledVersion(pending_ecu.first.ToString(), *pending_version,
1493 InstalledVersionUpdateMode::kCurrent);
1495 report_queue->enqueue(std_::make_unique<EcuInstallationCompletedReport>(
1496 pending_ecu.first, pending_version->correlation_id(),
true));
1499 std::string raw_report;
1500 computeDeviceInstallationResult(&ir, &raw_report);
1501 storage->storeDeviceInstallationResult(ir, raw_report, pending_version->correlation_id());
1507 boost::optional<Uptane::HardwareIdentifier> SotaUptaneClient::ecuHwId(
const Uptane::EcuSerial &serial)
const {
1508 if (serial == primary_ecu_serial_ || serial.ToString().empty()) {
1509 if (primary_ecu_hw_id_ == Uptane::HardwareIdentifier::Unknown()) {
1512 return primary_ecu_hw_id_;
1515 const auto it = secondaries.find(serial);
1516 if (it != secondaries.end()) {
1517 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 of a file or Uptane metadata.
Container for information about available updates.
Results of libaktualizr API calls.
bool report_network
Report device network information: IP address, hostname, MAC address.