Aktualizr
C++ SOTA Client
All Classes Namespaces Files Functions Variables Enumerations Enumerator Pages
sotauptaneclient.cc
1 #include "sotauptaneclient.h"
2 
3 #include <fnmatch.h>
4 #include <unistd.h>
5 #include <memory>
6 #include <utility>
7 
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"
14 
15 #include "utilities/fault_injection.h"
16 #include "utilities/utils.h"
17 
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) {
21  return;
22  }
23  auto event = std::make_shared<event::DownloadProgressReport>(target, description, progress);
24  (*channel)(event);
25 }
26 
27 void SotaUptaneClient::addSecondary(const std::shared_ptr<Uptane::SecondaryInterface> &sec) {
28  Uptane::EcuSerial serial = sec->getSerial();
29 
30  SecondaryInfo info;
31  if (!storage->loadSecondaryInfo(serial, &info) || info.type == "" || info.pub_key.Type() == KeyType::kUnknown) {
32  info.serial = serial;
33  info.hw_id = sec->getHwId();
34  info.type = sec->Type();
35  const PublicKey &p = sec->getPublicKey();
36  if (p.Type() != KeyType::kUnknown) {
37  info.pub_key = p;
38  }
39  storage->saveSecondaryInfo(info.serial, info.type, info.pub_key);
40  }
41 
42  if (storage->loadEcuRegistered()) {
43  EcuSerials serials;
44  storage->loadEcuSerials(&serials);
45  SerialCompare secondary_comp(serial);
46  if (std::find_if(serials.cbegin(), serials.cend(), secondary_comp) == serials.cend()) {
47  throw std::logic_error("Add new secondaries for provisioned device is not implemented yet");
48  }
49  }
50 
51  const auto map_it = secondaries.find(serial);
52  if (map_it != secondaries.end()) {
53  throw std::runtime_error(std::string("Multiple secondaries found with the same serial: ") + serial.ToString());
54  }
55 
56  secondaries.emplace(serial, sec);
57 }
58 
59 bool SotaUptaneClient::isInstalledOnPrimary(const Uptane::Target &target) {
60  if (target.ecus().find(primaryEcuSerial()) != target.ecus().end()) {
61  return target.MatchTarget(package_manager_->getCurrent());
62  }
63  return false;
64 }
65 
66 std::vector<Uptane::Target> SotaUptaneClient::findForEcu(const std::vector<Uptane::Target> &targets,
67  const Uptane::EcuSerial &ecu_id) {
68  std::vector<Uptane::Target> result;
69  for (auto it = targets.begin(); it != targets.end(); ++it) {
70  if (it->ecus().find(ecu_id) != it->ecus().end()) {
71  result.push_back(*it);
72  }
73  }
74  return result;
75 }
76 
77 data::InstallationResult SotaUptaneClient::PackageInstall(const Uptane::Target &target) {
78  LOG_INFO << "Installing package using " << package_manager_->name() << " package manager";
79  try {
80  return package_manager_->install(target);
81  } catch (std::exception &ex) {
83  }
84 }
85 
86 void SotaUptaneClient::finalizeAfterReboot() {
87  // TODO: consider bringing checkAndUpdatePendingSecondaries and the following functionality
88  // to the common denominator
89  if (!hasPendingUpdates()) {
90  LOG_DEBUG << "No pending updates, continuing with initialization";
91  return;
92  }
93 
94  LOG_INFO << "Checking for a pending update to apply for Primary ECU";
95 
96  const Uptane::EcuSerial &primary_ecu_serial = primaryEcuSerial();
97  boost::optional<Uptane::Target> pending_target;
98  storage->loadInstalledVersions(primary_ecu_serial.ToString(), nullptr, &pending_target);
99 
100  if (!pending_target) {
101  LOG_ERROR << "No pending update for Primary ECU found, continuing with initialization";
102  return;
103  }
104 
105  LOG_INFO << "Pending update for Primary ECU was found, trying to apply it...";
106 
107  data::InstallationResult install_res = package_manager_->finalizeInstall(*pending_target);
108 
109  if (install_res.result_code == data::ResultCode::Numeric::kNeedCompletion) {
110  LOG_INFO << "Pending update for Primary ECU was not applied because reboot was not detected, "
111  "continuing with initialization";
112  return;
113  }
114 
115  storage->saveEcuInstallationResult(primary_ecu_serial, install_res);
116 
117  const std::string correlation_id = pending_target->correlation_id();
118  if (install_res.success) {
119  storage->saveInstalledVersion(primary_ecu_serial.ToString(), *pending_target, InstalledVersionUpdateMode::kCurrent);
120 
121  report_queue->enqueue(std_::make_unique<EcuInstallationCompletedReport>(primary_ecu_serial, correlation_id, true));
122  } else {
123  // finalize failed, unset pending flag so that the rest of the uptane process can go forward again
124  storage->saveInstalledVersion(primary_ecu_serial.ToString(), *pending_target, InstalledVersionUpdateMode::kNone);
125  report_queue->enqueue(std_::make_unique<EcuInstallationCompletedReport>(primary_ecu_serial, correlation_id, false));
126  }
127 
128  director_repo.dropTargets(*storage); // fix for OTA-2587, listen to backend again after end of install
129 
131  std::string raw_report;
132  computeDeviceInstallationResult(&ir, &raw_report);
133  storage->storeDeviceInstallationResult(ir, raw_report, correlation_id);
134  putManifestSimple();
135 }
136 
137 data::InstallationResult SotaUptaneClient::PackageInstallSetResult(const Uptane::Target &target) {
139  Uptane::EcuSerial ecu_serial = primaryEcuSerial();
140 
141  // This is to recover more gracefully if the install process was interrupted
142  // but ends up booting the new version anyway (e.g: ostree finished
143  // deploying but the device restarted before the final saveInstalledVersion
144  // was called).
145  // By storing the version in the table (as uninstalled), we can still pick
146  // up some metadata
147  // TODO: we do not detect the incomplete install at aktualizr start in that
148  // case, it should ideally report a meaningful error.
149  storage->saveInstalledVersion(ecu_serial.ToString(), target, InstalledVersionUpdateMode::kNone);
150 
151  result = PackageInstall(target);
152  if (result.result_code.num_code == data::ResultCode::Numeric::kOk) {
153  // simple case: update already completed
154  storage->saveInstalledVersion(ecu_serial.ToString(), target, InstalledVersionUpdateMode::kCurrent);
155  } else if (result.result_code.num_code == data::ResultCode::Numeric::kNeedCompletion) {
156  // ostree case: need reboot
157  storage->saveInstalledVersion(ecu_serial.ToString(), target, InstalledVersionUpdateMode::kPending);
158  }
159  storage->saveEcuInstallationResult(ecu_serial, result);
160  return result;
161 }
162 
163 void SotaUptaneClient::reportHwInfo() {
164  Json::Value hw_info = Utils::getHardwareInfo();
165  if (!hw_info.empty()) {
166  if (hw_info != last_hw_info_reported) {
167  if (http->put(config.tls.server + "/system_info", hw_info).isOk()) {
168  last_hw_info_reported = hw_info;
169  }
170  }
171  } else {
172  LOG_WARNING << "Unable to fetch hardware information from host system.";
173  }
174 }
175 
176 void SotaUptaneClient::reportInstalledPackages() {
177  http->put(config.tls.server + "/core/installed", package_manager_->getInstalledPackages());
178 }
179 
180 void SotaUptaneClient::reportNetworkInfo() {
181  if (config.telemetry.report_network) {
182  LOG_DEBUG << "Reporting network information";
183  Json::Value network_info = Utils::getNetworkInfo();
184  if (network_info != last_network_info_reported) {
185  HttpResponse response = http->put(config.tls.server + "/system_info/network", network_info);
186  if (response.isOk()) {
187  last_network_info_reported = network_info;
188  }
189  }
190 
191  } else {
192  LOG_DEBUG << "Not reporting network information because telemetry is disabled";
193  }
194 }
195 
196 void SotaUptaneClient::reportAktualizrConfiguration() {
197  if (!config.telemetry.report_config) {
198  LOG_DEBUG << "Not reporting network information because telemetry is disabled";
199  return;
200  }
201 
202  LOG_DEBUG << "Reporting libaktualizr configuration";
203  std::stringstream conf_ss;
204  config.writeToStream(conf_ss);
205  http->post(config.tls.server + "/system_info/config", "application/toml", conf_ss.str());
206 }
207 
208 Json::Value SotaUptaneClient::AssembleManifest() {
209  Json::Value manifest; // signed top-level
210  Uptane::EcuSerial primary_ecu_serial = primaryEcuSerial();
211  manifest["primary_ecu_serial"] = primary_ecu_serial.ToString();
212 
213  // first part: report current version/state of all ecus
214  Json::Value version_manifest;
215 
216  Json::Value primary_manifest = uptane_manifest->assembleManifest(package_manager_->getCurrent());
217  std::vector<std::pair<Uptane::EcuSerial, int64_t>> ecu_cnt;
218  std::string report_counter;
219  if (!storage->loadEcuReportCounter(&ecu_cnt) || (ecu_cnt.size() == 0)) {
220  LOG_ERROR << "No ECU version report counter, please check the database!";
221  // TODO: consider not sending manifest at all in this case, or maybe retry
222  } else {
223  report_counter = std::to_string(ecu_cnt[0].second + 1);
224  storage->saveEcuReportCounter(ecu_cnt[0].first, ecu_cnt[0].second + 1);
225  }
226  version_manifest[primary_ecu_serial.ToString()] = uptane_manifest->sign(primary_manifest, report_counter);
227 
228  for (auto it = secondaries.begin(); it != secondaries.end(); it++) {
229  const Uptane::EcuSerial &ecu_serial = it->first;
230  Uptane::Manifest secmanifest = it->second->getManifest();
231 
232  bool from_cache = false;
233  if (secmanifest == Json::Value()) {
234  // could not get the secondary manifest, a cached value will be provided
235  std::string cached;
236  if (storage->loadCachedEcuManifest(ecu_serial, &cached)) {
237  LOG_WARNING << "Could not reach secondary " << ecu_serial << ", sending a cached version of its manifest";
238  secmanifest = Utils::parseJSON(cached);
239  from_cache = true;
240  } else {
241  LOG_ERROR << "Could not fetch a valid secondary manifest from cache";
242  }
243  }
244 
245  if (secmanifest.verifySignature(it->second->getPublicKey())) {
246  version_manifest[ecu_serial.ToString()] = secmanifest;
247  if (!from_cache) {
248  storage->storeCachedEcuManifest(ecu_serial, Utils::jsonToCanonicalStr(secmanifest));
249  }
250  } else {
251  // TODO(OTA-4305): send a corresponding event/report in this case
252  LOG_ERROR << "Secondary manifest is corrupted or not signed, or signature is invalid manifest: " << secmanifest;
253  }
254  }
255  manifest["ecu_version_manifests"] = version_manifest;
256 
257  // second part: report installation results
258  Json::Value installation_report;
259 
260  data::InstallationResult dev_result;
261  std::string raw_report;
262  std::string correlation_id;
263  bool has_results = storage->loadDeviceInstallationResult(&dev_result, &raw_report, &correlation_id);
264  if (has_results) {
265  installation_report["result"] = dev_result.toJson();
266  installation_report["raw_report"] = raw_report;
267  installation_report["correlation_id"] = correlation_id;
268  installation_report["items"] = Json::arrayValue;
269 
270  std::vector<std::pair<Uptane::EcuSerial, data::InstallationResult>> ecu_results;
271  storage->loadEcuInstallationResults(&ecu_results);
272  for (const auto &r : ecu_results) {
273  Uptane::EcuSerial serial = r.first;
274  data::InstallationResult res = r.second;
275 
276  Json::Value item;
277  item["ecu"] = serial.ToString();
278  item["result"] = res.toJson();
279 
280  installation_report["items"].append(item);
281  }
282 
283  manifest["installation_report"]["content_type"] = "application/vnd.com.here.otac.installationReport.v1";
284  manifest["installation_report"]["report"] = installation_report;
285  } else {
286  LOG_DEBUG << "No installation result to report in manifest";
287  }
288 
289  return manifest;
290 }
291 
292 bool SotaUptaneClient::hasPendingUpdates() const { return storage->hasPendingInstall(); }
293 
294 void SotaUptaneClient::initialize() {
295  LOG_DEBUG << "Checking if device is provisioned...";
296  auto keys = std::make_shared<KeyManager>(storage, config.keymanagerConfig());
297  Initializer initializer(config.provision, storage, http, *keys, secondaries);
298 
299  if (!initializer.isSuccessful()) {
300  throw std::runtime_error("Fatal error during provisioning or ECU device registration.");
301  }
302 
303  EcuSerials serials;
304  if (!storage->loadEcuSerials(&serials) || serials.size() == 0) {
305  throw std::runtime_error("Unable to load ECU serials after device registration.");
306  }
307 
308  uptane_manifest = std::make_shared<Uptane::ManifestIssuer>(keys, serials[0].first);
309  primary_ecu_serial_ = serials[0].first;
310  primary_ecu_hw_id_ = serials[0].second;
311  LOG_INFO << "Primary ECU serial: " << primary_ecu_serial_ << " with hardware ID: " << primary_ecu_hw_id_;
312 
313  verifySecondaries();
314 
315  std::string device_id;
316  if (!storage->loadDeviceId(&device_id)) {
317  throw std::runtime_error("Unable to load device ID after device registration.");
318  }
319  LOG_INFO << "Device ID: " << device_id;
320  LOG_INFO << "Device Gateway URL: " << config.tls.server;
321 
322  std::string subject;
323  std::string issuer;
324  std::string not_before;
325  std::string not_after;
326  keys->getCertInfo(&subject, &issuer, &not_before, &not_after);
327  LOG_INFO << "Certificate subject: " << subject;
328  LOG_INFO << "Certificate issuer: " << issuer;
329  LOG_INFO << "Certificate valid from: " << not_before << " until: " << not_after;
330 
331  LOG_DEBUG << "... provisioned OK";
332  finalizeAfterReboot();
333 }
334 
335 bool SotaUptaneClient::updateDirectorMeta() {
336  if (!director_repo.updateMeta(*storage, *uptane_fetcher)) {
337  last_exception = director_repo.getLastException();
338  return false;
339  }
340  return true;
341 }
342 
343 bool SotaUptaneClient::updateImagesMeta() {
344  if (!images_repo.updateMeta(*storage, *uptane_fetcher)) {
345  last_exception = images_repo.getLastException();
346  return false;
347  }
348  return true;
349 }
350 
351 bool SotaUptaneClient::checkDirectorMetaOffline() {
352  if (!director_repo.checkMetaOffline(*storage)) {
353  last_exception = director_repo.getLastException();
354  return false;
355  }
356  return true;
357 }
358 
359 bool SotaUptaneClient::checkImagesMetaOffline() {
360  if (!images_repo.checkMetaOffline(*storage)) {
361  last_exception = images_repo.getLastException();
362  return false;
363  }
364  return true;
365 }
366 
367 void SotaUptaneClient::computeDeviceInstallationResult(data::InstallationResult *result,
368  std::string *raw_installation_report) const {
369  data::InstallationResult device_installation_result =
370  data::InstallationResult(data::ResultCode::Numeric::kOk, "Device has been successfully installed");
371  std::string raw_ir = "Installation succesful";
372 
373  do {
374  std::vector<std::pair<Uptane::EcuSerial, data::InstallationResult>> ecu_results;
375 
376  if (!storage->loadEcuInstallationResults(&ecu_results)) {
377  // failed to load ECUs' installation result
379  "Unable to get installation results from ecus");
380  raw_ir = "Failed to load ECUs' installation result";
381 
382  break;
383  }
384 
385  std::string result_code_err_str;
386 
387  for (const auto &r : ecu_results) {
388  auto ecu_serial = r.first;
389  auto installation_res = r.second;
390 
391  auto hw_id = ecuHwId(ecu_serial);
392 
393  if (!hw_id) {
394  // couldn't find any ECU with the given serial/ID
396  "Unable to get installation results from ecus");
397 
398  raw_ir = "Couldn't find any ECU with the given serial: " + ecu_serial.ToString();
399 
400  break;
401  }
402 
403  if (installation_res.needCompletion()) {
404  // one of the ECUs needs completion, aka an installation finalization
405  device_installation_result =
406  data::InstallationResult(data::ResultCode::Numeric::kNeedCompletion,
407  "ECU needs completion/finalization to be installed: " + ecu_serial.ToString());
408  raw_ir = "ECU needs completion/finalization to be installed: " + ecu_serial.ToString();
409 
410  break;
411  }
412 
413  // format:
414  // ecu1_hwid:failure1|ecu2_hwid:failure2
415  if (!installation_res.isSuccess()) {
416  std::string ecu_code_str = (*hw_id).ToString() + ":" + installation_res.result_code.toString();
417  result_code_err_str += (result_code_err_str != "" ? "|" : "") + ecu_code_str;
418  }
419  }
420 
421  if (!result_code_err_str.empty()) {
422  // installation on at least one of the ECUs has failed
423  device_installation_result =
425  "Installation failed on at least one of ECUs");
426  raw_ir = "Installation failed on at least one of ECUs";
427 
428  break;
429  }
430 
431  } while (false);
432 
433  if (result != nullptr) {
434  *result = device_installation_result;
435  }
436 
437  if (raw_installation_report != nullptr) {
438  *raw_installation_report = raw_ir;
439  }
440 }
441 
442 bool SotaUptaneClient::getNewTargets(std::vector<Uptane::Target> *new_targets, unsigned int *ecus_count) {
443  std::vector<Uptane::Target> targets = director_repo.getTargets().targets;
444  Uptane::EcuSerial primary_ecu_serial = primaryEcuSerial();
445  if (ecus_count != nullptr) {
446  *ecus_count = 0;
447  }
448  for (const Uptane::Target &target : targets) {
449  bool is_new = false;
450  for (const auto &ecu : target.ecus()) {
451  Uptane::EcuSerial ecu_serial = ecu.first;
452  Uptane::HardwareIdentifier hw_id = ecu.second;
453 
454  auto hw_id_known = ecuHwId(ecu_serial);
455  if (!hw_id_known) {
456  LOG_ERROR << "Unknown ECU ID in director targets metadata: " << ecu_serial.ToString();
457  last_exception = Uptane::BadEcuId(target.filename());
458  return false;
459  }
460 
461  if (*hw_id_known != hw_id) {
462  LOG_ERROR << "Wrong hardware identifier for ECU " << ecu_serial.ToString();
463  last_exception = Uptane::BadHardwareId(target.filename());
464  return false;
465  }
466 
467  boost::optional<Uptane::Target> current_version;
468  if (!storage->loadInstalledVersions(ecu_serial.ToString(), &current_version, nullptr)) {
469  LOG_WARNING << "Could not load currently installed version for ECU ID: " << ecu_serial.ToString();
470  break;
471  }
472 
473  if (!current_version) {
474  LOG_WARNING << "Current version for ECU ID: " << ecu_serial.ToString() << " is unknown";
475  is_new = true;
476  } else if (current_version->filename() != target.filename()) {
477  is_new = true;
478  }
479 
480  if (primary_ecu_serial == ecu_serial) {
481  if (!target.IsOstree() &&
482  (config.pacman.type == PACKAGE_MANAGER_OSTREE || config.pacman.type == PACKAGE_MANAGER_OSTREEDOCKERAPP)) {
483  LOG_ERROR << "Cannot install a non-OSTree package on an OSTree system";
484  last_exception = Uptane::InvalidTarget(target.filename());
485  return false;
486  }
487  }
488 
489  if (is_new && ecus_count != nullptr) {
490  (*ecus_count)++;
491  }
492  // no updates for this image => continue
493  }
494  if (is_new) {
495  new_targets->push_back(target);
496  }
497  }
498  return true;
499 }
500 
501 std::unique_ptr<Uptane::Target> SotaUptaneClient::findTargetHelper(const Uptane::Targets &cur_targets,
502  const Uptane::Target &queried_target,
503  const int level, const bool terminating,
504  const bool offline) {
505  TargetCompare target_comp(queried_target);
506  const auto it = std::find_if(cur_targets.targets.cbegin(), cur_targets.targets.cend(), target_comp);
507  if (it != cur_targets.targets.cend()) {
508  return std_::make_unique<Uptane::Target>(*it);
509  }
510 
511  if (terminating || level >= Uptane::kDelegationsMaxDepth) {
512  return std::unique_ptr<Uptane::Target>(nullptr);
513  }
514 
515  for (const auto &delegate_name : cur_targets.delegated_role_names_) {
516  Uptane::Role delegate_role = Uptane::Role::Delegation(delegate_name);
517  auto patterns = cur_targets.paths_for_role_.find(delegate_role);
518  if (patterns == cur_targets.paths_for_role_.end()) {
519  continue;
520  }
521 
522  bool match = false;
523  for (const auto &pattern : patterns->second) {
524  if (fnmatch(pattern.c_str(), queried_target.filename().c_str(), 0) == 0) {
525  match = true;
526  break;
527  }
528  }
529  if (!match) {
530  continue;
531  }
532 
533  // Target name matches one of the patterns
534 
535  auto delegation =
536  Uptane::getTrustedDelegation(delegate_role, cur_targets, images_repo, *storage, *uptane_fetcher, offline);
537  if (delegation.isExpired(TimeStamp::Now())) {
538  continue;
539  }
540 
541  auto is_terminating = cur_targets.terminating_role_.find(delegate_role);
542  if (is_terminating == cur_targets.terminating_role_.end()) {
543  throw Uptane::Exception("images", "Inconsistent delegations");
544  }
545 
546  auto found_target = findTargetHelper(delegation, queried_target, level + 1, is_terminating->second, offline);
547  if (found_target != nullptr) {
548  return found_target;
549  }
550  }
551 
552  return std::unique_ptr<Uptane::Target>(nullptr);
553 }
554 
555 std::unique_ptr<Uptane::Target> SotaUptaneClient::findTargetInDelegationTree(const Uptane::Target &target,
556  const bool offline) {
557  auto toplevel_targets = images_repo.getTargets();
558  if (toplevel_targets == nullptr) {
559  return std::unique_ptr<Uptane::Target>(nullptr);
560  }
561 
562  return findTargetHelper(*toplevel_targets, target, 0, false, offline);
563 }
564 
565 result::Download SotaUptaneClient::downloadImages(const std::vector<Uptane::Target> &targets,
566  const api::FlowControlToken *token) {
567  // Uptane step 4 - download all the images and verify them against the metadata (for OSTree - pull without
568  // deploying)
569  std::lock_guard<std::mutex> guard(download_mutex);
571  std::vector<Uptane::Target> downloaded_targets;
572 
573  result::UpdateStatus update_status = checkUpdatesOffline(targets);
574  if (update_status != result::UpdateStatus::kUpdatesAvailable) {
575  if (update_status == result::UpdateStatus::kNoUpdatesAvailable) {
576  result = result::Download({}, result::DownloadStatus::kNothingToDownload, "");
577  } else {
578  result =
579  result::Download(downloaded_targets, result::DownloadStatus::kError, "Error rechecking stored metadata.");
580  storeInstallationFailure(
581  data::InstallationResult(data::ResultCode::Numeric::kInternalError, "Error rechecking stored metadata."));
582  }
583  sendEvent<event::AllDownloadsComplete>(result);
584  return result;
585  }
586 
587  for (const auto &target : targets) {
588  auto res = downloadImage(target, token);
589  if (res.first) {
590  downloaded_targets.push_back(res.second);
591  }
592  }
593 
594  if (targets.size() == downloaded_targets.size()) {
595  result = result::Download(downloaded_targets, result::DownloadStatus::kSuccess, "");
596  } else {
597  if (downloaded_targets.size() == 0) {
598  LOG_ERROR << "None of " << targets.size() << " targets were successfully downloaded.";
599  result = result::Download(downloaded_targets, result::DownloadStatus::kError, "Each target download has failed");
600  } else {
601  LOG_ERROR << "Only " << downloaded_targets.size() << " of " << targets.size() << " were successfully downloaded.";
602  result = result::Download(downloaded_targets, result::DownloadStatus::kPartialSuccess, "");
603  }
604  storeInstallationFailure(
606  }
607 
608  sendEvent<event::AllDownloadsComplete>(result);
609  return result;
610 }
611 
612 void SotaUptaneClient::reportPause() {
613  const std::string &correlation_id = director_repo.getCorrelationId();
614  report_queue->enqueue(std_::make_unique<DevicePausedReport>(correlation_id));
615 }
616 
617 void SotaUptaneClient::reportResume() {
618  const std::string &correlation_id = director_repo.getCorrelationId();
619  report_queue->enqueue(std_::make_unique<DeviceResumedReport>(correlation_id));
620 }
621 
622 std::pair<bool, Uptane::Target> SotaUptaneClient::downloadImage(const Uptane::Target &target,
623  const api::FlowControlToken *token) {
624  // TODO: support downloading encrypted targets from director
625 
626  const std::string &correlation_id = director_repo.getCorrelationId();
627  // send an event for all ecus that are touched by this target
628  for (const auto &ecu : target.ecus()) {
629  report_queue->enqueue(std_::make_unique<EcuDownloadStartedReport>(ecu.first, correlation_id));
630  }
631 
632  KeyManager keys(storage, config.keymanagerConfig());
633  keys.loadKeys();
634  auto prog_cb = [this](const Uptane::Target &t, const std::string description, unsigned int progress) {
635  report_progress_cb(events_channel.get(), t, description, progress);
636  };
637 
638  bool success = false;
639  const Uptane::EcuSerial &primary_ecu_serial = primaryEcuSerial();
640 
641  if (target.IsForEcu(primary_ecu_serial) || !target.IsOstree()) {
642  // TODO: download should be the logical ECU and packman specific
643  const int max_tries = 3;
644  int tries = 0;
645  std::chrono::milliseconds wait(500);
646 
647  for (; tries < max_tries; tries++) {
648  success = package_manager_->fetchTarget(target, *uptane_fetcher, keys, prog_cb, token);
649  if (success) {
650  break;
651  } else if (tries < max_tries - 1) {
652  std::this_thread::sleep_for(wait);
653  wait *= 2;
654  }
655  }
656  if (!success) {
657  LOG_ERROR << "Download unsuccessful after " << tries << " attempts.";
658  }
659  } else {
660  // we emulate successfull download in case of the secondary ostree update
661  success = true;
662  }
663 
664  // send this asynchronously before `sendEvent`, so that the report timestamp
665  // would not be delayed by callbacks on events
666  for (const auto &ecu : target.ecus()) {
667  report_queue->enqueue(std_::make_unique<EcuDownloadCompletedReport>(ecu.first, correlation_id, success));
668  }
669 
670  sendEvent<event::DownloadTargetComplete>(target, success);
671  return {success, target};
672 }
673 
674 bool SotaUptaneClient::uptaneIteration(std::vector<Uptane::Target> *targets, unsigned int *ecus_count) {
675  if (!updateDirectorMeta()) {
676  LOG_ERROR << "Failed to update director metadata: " << last_exception.what();
677  return false;
678  }
679  std::vector<Uptane::Target> tmp_targets;
680  unsigned int ecus;
681  if (!getNewTargets(&tmp_targets, &ecus)) {
682  LOG_ERROR << "Inconsistency between director metadata and existent ECUs";
683  return false;
684  }
685 
686  if (tmp_targets.empty()) {
687  if (targets != nullptr) {
688  *targets = std::move(tmp_targets);
689  }
690  if (ecus_count != nullptr) {
691  *ecus_count = ecus;
692  }
693  return true;
694  }
695 
696  LOG_INFO << "got new updates";
697 
698  if (!updateImagesMeta()) {
699  LOG_ERROR << "Failed to update images metadata: " << last_exception.what();
700  return false;
701  }
702 
703  if (targets != nullptr) {
704  *targets = std::move(tmp_targets);
705  }
706  if (ecus_count != nullptr) {
707  *ecus_count = ecus;
708  }
709  return true;
710 }
711 
712 bool SotaUptaneClient::uptaneOfflineIteration(std::vector<Uptane::Target> *targets, unsigned int *ecus_count) {
713  if (!checkDirectorMetaOffline()) {
714  LOG_ERROR << "Failed to check director metadata: " << last_exception.what();
715  return false;
716  }
717  std::vector<Uptane::Target> tmp_targets;
718  unsigned int ecus;
719  if (!getNewTargets(&tmp_targets, &ecus)) {
720  LOG_ERROR << "Inconsistency between director metadata and existent ECUs";
721  return false;
722  }
723 
724  if (tmp_targets.empty()) {
725  *targets = std::move(tmp_targets);
726  if (ecus_count != nullptr) {
727  *ecus_count = ecus;
728  }
729  return true;
730  }
731 
732  if (!checkImagesMetaOffline()) {
733  LOG_ERROR << "Failed to check images metadata: " << last_exception.what();
734  return false;
735  }
736 
737  *targets = std::move(tmp_targets);
738  if (ecus_count != nullptr) {
739  *ecus_count = ecus;
740  }
741  return true;
742 }
743 
744 void SotaUptaneClient::sendDeviceData() {
745  reportHwInfo();
746  reportInstalledPackages();
747  reportNetworkInfo();
748  reportAktualizrConfiguration();
749  putManifestSimple();
750  sendEvent<event::SendDeviceDataComplete>();
751 }
752 
753 result::UpdateCheck SotaUptaneClient::fetchMeta() {
755 
756  reportNetworkInfo();
757 
758  if (hasPendingUpdates()) {
759  // if there are some pending updates check if the secondaries' pending updates have been applied
760  LOG_INFO << "The current update is pending. Check if pending ECUs has been already updated";
761  checkAndUpdatePendingSecondaries();
762  }
763 
764  if (hasPendingUpdates()) {
765  // if there are still some pending updates just return, don't check for new updates
766  // no need in update checking if there are some pending updates
767  LOG_INFO << "An update is pending. Skipping check for update until installation is complete.";
768  return result::UpdateCheck({}, 0, result::UpdateStatus::kError, Json::nullValue,
769  "There are pending updates, no new updates are checked");
770  }
771 
772  // Uptane step 1 (build the vehicle version manifest):
773  if (!putManifestSimple()) {
774  LOG_ERROR << "Error sending manifest!";
775  }
776  result = checkUpdates();
777  sendEvent<event::UpdateCheckComplete>(result);
778 
779  return result;
780 }
781 
782 result::UpdateCheck SotaUptaneClient::checkUpdates() {
784 
785  std::vector<Uptane::Target> updates;
786  unsigned int ecus_count = 0;
787  if (!uptaneIteration(&updates, &ecus_count)) {
788  result = result::UpdateCheck({}, 0, result::UpdateStatus::kError, Json::nullValue, "Could not update metadata.");
789  return result;
790  }
791 
792  std::string director_targets;
793  storage->loadNonRoot(&director_targets, Uptane::RepositoryType::Director(), Uptane::Role::Targets());
794 
795  if (updates.empty()) {
796  LOG_DEBUG << "No new updates found in Uptane metadata.";
797  result =
798  result::UpdateCheck({}, 0, result::UpdateStatus::kNoUpdatesAvailable, Utils::parseJSON(director_targets), "");
799  return result;
800  }
801 
802  // 5.4.4.2.10.: Verify that Targets metadata from the Director and Image
803  // repositories match. A Primary ECU MUST perform this check on metadata for
804  // all images listed in the Targets metadata file from the Director
805  // repository.
806  for (auto &target : updates) {
807  auto images_target = findTargetInDelegationTree(target, false);
808  if (images_target == nullptr) {
809  // TODO: Could also be a missing target or delegation expiration.
810  last_exception = Uptane::TargetMismatch(target.filename());
811  LOG_ERROR << "No matching target in images targets metadata for " << target;
812  result = result::UpdateCheck({}, 0, result::UpdateStatus::kError, Utils::parseJSON(director_targets),
813  "Target mismatch.");
814  storeInstallationFailure(
816  return result;
817  }
818  // If the URL from the Director is unset, but the URL from the Images repo
819  // is set, use that.
820  if (target.uri().empty() && !images_target->uri().empty()) {
821  target.setUri(images_target->uri());
822  }
823  }
824 
825  result = result::UpdateCheck(updates, ecus_count, result::UpdateStatus::kUpdatesAvailable,
826  Utils::parseJSON(director_targets), "");
827  if (updates.size() == 1) {
828  LOG_INFO << "1 new update found in Uptane metadata.";
829  } else {
830  LOG_INFO << updates.size() << " new updates found in Uptane metadata.";
831  }
832  return result;
833 }
834 
835 result::UpdateStatus SotaUptaneClient::checkUpdatesOffline(const std::vector<Uptane::Target> &targets) {
836  if (hasPendingUpdates()) {
837  // no need in update checking if there are some pending updates
838  LOG_INFO << "An update is pending. Skipping stored metadata check until installation is complete.";
839  return result::UpdateStatus::kError;
840  }
841 
842  if (targets.empty()) {
843  LOG_WARNING << "Requested targets vector is empty. Nothing to do.";
844  return result::UpdateStatus::kError;
845  }
846 
847  std::vector<Uptane::Target> director_targets;
848  unsigned int ecus_count = 0;
849  if (!uptaneOfflineIteration(&director_targets, &ecus_count)) {
850  LOG_ERROR << "Invalid Uptane metadata in storage.";
851  return result::UpdateStatus::kError;
852  }
853 
854  if (director_targets.empty()) {
855  LOG_ERROR << "No new updates found in Uptane metadata, but expected " << targets.size() << ".";
856  return result::UpdateStatus::kNoUpdatesAvailable;
857  }
858 
859  // For every target in the Director Targets metadata, walk the delegation
860  // tree (if necessary) and find a matching target in the Images repo
861  // metadata.
862  for (const auto &target : targets) {
863  TargetCompare target_comp(target);
864  const auto it = std::find_if(director_targets.cbegin(), director_targets.cend(), target_comp);
865  if (it == director_targets.cend()) {
866  LOG_ERROR << "No matching target in director targets metadata for " << target;
867  return result::UpdateStatus::kError;
868  }
869 
870  const auto images_target = findTargetInDelegationTree(target, true);
871  if (images_target == nullptr) {
872  LOG_ERROR << "No matching target in images targets metadata for " << target;
873  return result::UpdateStatus::kError;
874  }
875  }
876 
877  return result::UpdateStatus::kUpdatesAvailable;
878 }
879 
880 result::Install SotaUptaneClient::uptaneInstall(const std::vector<Uptane::Target> &updates) {
881  const std::string &correlation_id = director_repo.getCorrelationId();
882 
883  // clear all old results first
884  storage->clearInstallationResults();
885 
886  // put most of the logic in a lambda so that we can take care of common
887  // post-operations
888  result::Install r;
889  std::string raw_report;
890  bool drop_targets = false;
891 
892  std::tie(r, raw_report, drop_targets) = [this, &updates,
893  &correlation_id]() -> std::tuple<result::Install, std::string, bool> {
895 
896  // Recheck the Uptane metadata and make sure the requested updates are
897  // consistent with the stored metadata.
898  result::UpdateStatus update_status = checkUpdatesOffline(updates);
899  if (update_status != result::UpdateStatus::kUpdatesAvailable) {
900  if (update_status == result::UpdateStatus::kNoUpdatesAvailable) {
901  result.dev_report = {false, data::ResultCode::Numeric::kAlreadyProcessed, ""};
902  } else {
903  result.dev_report = {false, data::ResultCode::Numeric::kInternalError, ""};
904  }
905  return std::make_tuple(result, "Stored Uptane metadata is invalid", false);
906  }
907 
908  Uptane::EcuSerial primary_ecu_serial = primaryEcuSerial();
909  // Recheck the downloaded update hashes.
910  for (const auto &update : updates) {
911  if (update.IsForEcu(primary_ecu_serial) || !update.IsOstree()) {
912  // download binary images for any target, for both Primary and Secondary
913  // download an ostree revision just for Primary, Secondary will do it by itself
914  // Primary cannot verify downloaded OSTree targets for Secondaries,
915  // Downloading of Secondary's ostree repo revision to the Primary's can fail
916  // if they differ signficantly as ostree has a certain cap/limit of the diff it pulls
917  if (package_manager_->verifyTarget(update) != TargetStatus::kGood) {
918  result.dev_report = {false, data::ResultCode::Numeric::kInternalError, ""};
919  return std::make_tuple(result, "Downloaded target is invalid", false);
920  }
921  }
922  }
923 
924  // wait some time for secondaries to come up
925  // note: this fail after a time out but will be retried at the next install
926  // phase if the targets have not been changed. This is done to avoid being
927  // stuck in an unrecoverable state here
928  if (!waitSecondariesReachable(updates)) {
929  result.dev_report = {false, data::ResultCode::Numeric::kInternalError, "Unreachable secondary"};
930  return std::make_tuple(result, "Secondaries were not available", false);
931  }
932 
933  // Uptane step 5 (send time to all ECUs) is not implemented yet.
934  std::vector<Uptane::Target> primary_updates = findForEcu(updates, primary_ecu_serial);
935 
936  // 6 - send metadata to all the ECUs
937  sendMetadataToEcus(updates);
938 
939  // 7 - send images to ECUs (deploy for OSTree)
940  if (primary_updates.size() != 0u) {
941  // assuming one OSTree OS per primary => there can be only one OSTree update
942  Uptane::Target primary_update = primary_updates[0];
943  primary_update.setCorrelationId(correlation_id);
944 
945  report_queue->enqueue(std_::make_unique<EcuInstallationStartedReport>(primary_ecu_serial, correlation_id));
946  sendEvent<event::InstallStarted>(primary_ecu_serial);
947 
948  data::InstallationResult install_res;
949  if (!isInstalledOnPrimary(primary_update)) {
950  // notify the bootloader before installation happens, because installation is not atomic and
951  // a false notification doesn't hurt when rollbacks are implemented
952  package_manager_->updateNotify();
953  install_res = PackageInstallSetResult(primary_update);
954  if (install_res.result_code.num_code == data::ResultCode::Numeric::kNeedCompletion) {
955  // update needs a reboot, send distinct EcuInstallationApplied event
956  report_queue->enqueue(std_::make_unique<EcuInstallationAppliedReport>(primary_ecu_serial, correlation_id));
957  sendEvent<event::InstallTargetComplete>(primary_ecu_serial, true);
958  } else if (install_res.result_code.num_code == data::ResultCode::Numeric::kOk) {
959  storage->saveEcuInstallationResult(primary_ecu_serial, install_res);
960  report_queue->enqueue(
961  std_::make_unique<EcuInstallationCompletedReport>(primary_ecu_serial, correlation_id, true));
962  sendEvent<event::InstallTargetComplete>(primary_ecu_serial, true);
963  } else {
964  // general error case
965  storage->saveEcuInstallationResult(primary_ecu_serial, install_res);
966  report_queue->enqueue(
967  std_::make_unique<EcuInstallationCompletedReport>(primary_ecu_serial, correlation_id, false));
968  sendEvent<event::InstallTargetComplete>(primary_ecu_serial, false);
969  }
970  } else {
971  install_res =
973  storage->saveEcuInstallationResult(primary_ecu_serial, install_res);
974  // TODO: distinguish this case from regular failure for local and remote event reporting
975  report_queue->enqueue(
976  std_::make_unique<EcuInstallationCompletedReport>(primary_ecu_serial, correlation_id, false));
977  sendEvent<event::InstallTargetComplete>(primary_ecu_serial, false);
978  }
979  result.ecu_reports.emplace(result.ecu_reports.begin(), primary_update, primary_ecu_serial, install_res);
980  } else {
981  LOG_INFO << "No update to install on primary";
982  }
983 
984  auto sec_reports = sendImagesToEcus(updates);
985  result.ecu_reports.insert(result.ecu_reports.end(), sec_reports.begin(), sec_reports.end());
986  std::string rr;
987  computeDeviceInstallationResult(&result.dev_report, &rr);
988 
989  return std::make_tuple(result, rr, !(result.dev_report.isSuccess() || result.dev_report.needCompletion()));
990  }();
991 
992  // TODO(OTA-2178): think of exception handling; the SQLite related code can throw exceptions
993  storage->storeDeviceInstallationResult(r.dev_report, raw_report, correlation_id);
994 
995  sendEvent<event::AllInstallsComplete>(r);
996 
997  if (drop_targets) {
998  director_repo.dropTargets(*storage); // fix for OTA-2587, listen to backend again after end of install
999  }
1000 
1001  return r;
1002 }
1003 
1004 result::CampaignCheck SotaUptaneClient::campaignCheck() {
1005  auto campaigns = campaign::Campaign::fetchAvailableCampaigns(*http, config.tls.server);
1006  for (const auto &c : campaigns) {
1007  LOG_INFO << "Campaign: " << c.name;
1008  LOG_INFO << "Campaign id: " << c.id;
1009  LOG_INFO << "Campaign size: " << c.size;
1010  LOG_INFO << "CampaignAccept required: " << (c.autoAccept ? "no" : "yes");
1011  LOG_INFO << "Message: " << c.description;
1012  }
1013  result::CampaignCheck result(campaigns);
1014  sendEvent<event::CampaignCheckComplete>(result);
1015  return result;
1016 }
1017 
1018 void SotaUptaneClient::campaignAccept(const std::string &campaign_id) {
1019  sendEvent<event::CampaignAcceptComplete>();
1020  report_queue->enqueue(std_::make_unique<CampaignAcceptedReport>(campaign_id));
1021 }
1022 
1023 void SotaUptaneClient::campaignDecline(const std::string &campaign_id) {
1024  sendEvent<event::CampaignDeclineComplete>();
1025  report_queue->enqueue(std_::make_unique<CampaignDeclinedReport>(campaign_id));
1026 }
1027 
1028 void SotaUptaneClient::campaignPostpone(const std::string &campaign_id) {
1029  sendEvent<event::CampaignPostponeComplete>();
1030  report_queue->enqueue(std_::make_unique<CampaignPostponedReport>(campaign_id));
1031 }
1032 
1033 bool SotaUptaneClient::isInstallCompletionRequired() const {
1034  bool force_install_completion = (hasPendingUpdates() && config.uptane.force_install_completion);
1035  return force_install_completion;
1036 }
1037 
1038 void SotaUptaneClient::completeInstall() const {
1039  if (isInstallCompletionRequired()) {
1040  package_manager_->completeInstall();
1041  }
1042 }
1043 
1044 bool SotaUptaneClient::putManifestSimple(const Json::Value &custom) {
1045  // does not send event, so it can be used as a subset of other steps
1046  if (hasPendingUpdates()) {
1047  // Debug level here because info level is annoying if the update check
1048  // frequency is low.
1049  LOG_DEBUG << "An update is pending. Skipping manifest upload until installation is complete.";
1050  return false;
1051  }
1052 
1053  static bool connected = true;
1054  auto manifest = AssembleManifest();
1055  if (custom != Json::nullValue) {
1056  manifest["custom"] = custom;
1057  }
1058  auto signed_manifest = uptane_manifest->sign(manifest);
1059  HttpResponse response = http->put(config.uptane.director_server + "/manifest", signed_manifest);
1060  if (response.isOk()) {
1061  if (!connected) {
1062  LOG_INFO << "Connectivity is restored.";
1063  }
1064  connected = true;
1065  storage->clearInstallationResults();
1066  return true;
1067  } else {
1068  connected = false;
1069  }
1070 
1071  LOG_WARNING << "Put manifest request failed: " << response.getStatusStr();
1072  return false;
1073 }
1074 
1075 bool SotaUptaneClient::putManifest(const Json::Value &custom) {
1076  bool success = putManifestSimple(custom);
1077  sendEvent<event::PutManifestComplete>(success);
1078  return success;
1079 }
1080 
1081 // Check stored secondaries list against secondaries known to aktualizr.
1082 void SotaUptaneClient::verifySecondaries() {
1083  storage->clearMisconfiguredEcus();
1084  EcuSerials serials;
1085  if (!storage->loadEcuSerials(&serials) || serials.empty()) {
1086  LOG_ERROR << "No ECU serials found in storage!";
1087  return;
1088  }
1089 
1090  std::vector<MisconfiguredEcu> misconfigured_ecus;
1091  std::vector<bool> found(serials.size(), false);
1092  SerialCompare primary_comp(primaryEcuSerial());
1093  EcuSerials::const_iterator store_it;
1094  store_it = std::find_if(serials.cbegin(), serials.cend(), primary_comp);
1095  if (store_it == serials.cend()) {
1096  LOG_ERROR << "Primary ECU serial " << primaryEcuSerial() << " not found in storage!";
1097  misconfigured_ecus.emplace_back(primaryEcuSerial(), Uptane::HardwareIdentifier(""), EcuState::kOld);
1098  } else {
1099  found[static_cast<size_t>(std::distance(serials.cbegin(), store_it))] = true;
1100  }
1101 
1102  for (auto it = secondaries.cbegin(); it != secondaries.cend(); ++it) {
1103  SerialCompare secondary_comp(it->second->getSerial());
1104  store_it = std::find_if(serials.cbegin(), serials.cend(), secondary_comp);
1105  if (store_it == serials.cend()) {
1106  LOG_ERROR << "Secondary ECU serial " << it->second->getSerial() << " (hardware ID " << it->second->getHwId()
1107  << ") not found in storage!";
1108  misconfigured_ecus.emplace_back(it->second->getSerial(), it->second->getHwId(), EcuState::kNotRegistered);
1109  } else if (found[static_cast<size_t>(std::distance(serials.cbegin(), store_it))]) {
1110  LOG_ERROR << "Secondary ECU serial " << it->second->getSerial() << " (hardware ID " << it->second->getHwId()
1111  << ") has a duplicate entry in storage!";
1112  } else {
1113  found[static_cast<size_t>(std::distance(serials.cbegin(), store_it))] = true;
1114  }
1115  }
1116 
1117  std::vector<bool>::iterator found_it;
1118  for (found_it = found.begin(); found_it != found.end(); ++found_it) {
1119  if (!*found_it) {
1120  auto not_registered = serials[static_cast<size_t>(std::distance(found.begin(), found_it))];
1121  LOG_WARNING << "ECU serial " << not_registered.first << " in storage was not reported to aktualizr!";
1122  misconfigured_ecus.emplace_back(not_registered.first, not_registered.second, EcuState::kOld);
1123  }
1124  }
1125 
1126  storage->storeMisconfiguredEcus(misconfigured_ecus);
1127 }
1128 
1129 bool SotaUptaneClient::waitSecondariesReachable(const std::vector<Uptane::Target> &updates) {
1130  std::map<Uptane::EcuSerial, Uptane::SecondaryInterface *> targeted_secondaries;
1131  const Uptane::EcuSerial &primary_ecu_serial = primaryEcuSerial();
1132  for (const auto &t : updates) {
1133  for (const auto &ecu : t.ecus()) {
1134  if (ecu.first == primary_ecu_serial) {
1135  continue;
1136  }
1137  auto f = secondaries.find(ecu.first);
1138  if (f == secondaries.end()) {
1139  LOG_ERROR << "Target " << t << " has unknown ECU ID";
1140  continue;
1141  }
1142 
1143  targeted_secondaries[ecu.first] = f->second.get();
1144  }
1145  }
1146 
1147  if (targeted_secondaries.empty()) {
1148  return true;
1149  }
1150 
1151  LOG_INFO << "Waiting for secondaries to connect to start installation...";
1152 
1153  auto deadline = std::chrono::system_clock::now() + std::chrono::seconds(config.uptane.secondary_preinstall_wait_sec);
1154  while (std::chrono::system_clock::now() <= deadline) {
1155  if (targeted_secondaries.empty()) {
1156  return true;
1157  }
1158 
1159  for (auto sec_it = targeted_secondaries.begin(); sec_it != targeted_secondaries.end();) {
1160  if (sec_it->second->ping()) {
1161  sec_it = targeted_secondaries.erase(sec_it);
1162  } else {
1163  sec_it++;
1164  }
1165  }
1166  std::this_thread::sleep_for(std::chrono::seconds(1));
1167  }
1168 
1169  LOG_ERROR << "Secondaries did not connect: ";
1170 
1171  for (const auto &sec : targeted_secondaries) {
1172  LOG_ERROR << sec.second->getSerial();
1173  }
1174 
1175  return false;
1176 }
1177 
1178 void SotaUptaneClient::storeInstallationFailure(const data::InstallationResult &result) {
1179  // Store installation report to inform Director of the update failure before
1180  // we actually got to the install step.
1181  const std::string &correlation_id = director_repo.getCorrelationId();
1182  storage->storeDeviceInstallationResult(result, "", correlation_id);
1183  // Fix for OTA-2587, listen to backend again after end of install.
1184  director_repo.dropTargets(*storage);
1185 }
1186 
1187 void SotaUptaneClient::rotateSecondaryRoot(Uptane::RepositoryType repo, Uptane::SecondaryInterface &secondary) {
1188  std::string latest_root;
1189 
1190  if (!storage->loadLatestRoot(&latest_root, repo)) {
1191  LOG_ERROR << "No root metadata to send";
1192  return;
1193  }
1194 
1195  int last_root_version = Uptane::extractVersionUntrusted(latest_root);
1196 
1197  int sec_root_version = secondary.getRootVersion((repo == Uptane::RepositoryType::Director()));
1198  if (sec_root_version >= 0) {
1199  for (int v = sec_root_version + 1; v <= last_root_version; v++) {
1200  std::string root;
1201  if (!storage->loadRoot(&root, repo, Uptane::Version(v))) {
1202  LOG_WARNING << "Couldn't find root meta in the storage, trying remote repo";
1203  if (!uptane_fetcher->fetchRole(&root, Uptane::kMaxRootSize, repo, Uptane::Role::Root(), Uptane::Version(v))) {
1204  // TODO(OTA-4552): looks problematic, robust procedure needs to be defined
1205  LOG_ERROR << "Root metadata could not be fetched, skipping to the next secondary";
1206  return;
1207  }
1208  }
1209  if (!secondary.putRoot(root, repo == Uptane::RepositoryType::Director())) {
1210  LOG_ERROR << "Sending metadata to " << secondary.getSerial() << " failed";
1211  }
1212  }
1213  }
1214 }
1215 
1216 // TODO(OTA-4342): the function can't currently return any errors. The problem of error reporting from
1217 // secondaries should be solved on a system (backend+frontend) error.
1218 // TODO: the function blocks until it updates all the secondaries. Consider non-blocking operation.
1219 void SotaUptaneClient::sendMetadataToEcus(const std::vector<Uptane::Target> &targets) {
1220  Uptane::RawMetaPack meta;
1221  if (!storage->loadLatestRoot(&meta.director_root, Uptane::RepositoryType::Director())) {
1222  LOG_ERROR << "No director root metadata to send";
1223  return;
1224  }
1225  if (!storage->loadNonRoot(&meta.director_targets, Uptane::RepositoryType::Director(), Uptane::Role::Targets())) {
1226  LOG_ERROR << "No director targets metadata to send";
1227  return;
1228  }
1229  if (!storage->loadLatestRoot(&meta.image_root, Uptane::RepositoryType::Image())) {
1230  LOG_ERROR << "No images root metadata to send";
1231  return;
1232  }
1233  if (!storage->loadNonRoot(&meta.image_timestamp, Uptane::RepositoryType::Image(), Uptane::Role::Timestamp())) {
1234  LOG_ERROR << "No images timestamp metadata to send";
1235  return;
1236  }
1237  if (!storage->loadNonRoot(&meta.image_snapshot, Uptane::RepositoryType::Image(), Uptane::Role::Snapshot())) {
1238  LOG_ERROR << "No images snapshot metadata to send";
1239  return;
1240  }
1241  if (!storage->loadNonRoot(&meta.image_targets, Uptane::RepositoryType::Image(), Uptane::Role::Targets())) {
1242  LOG_ERROR << "No images targets metadata to send";
1243  return;
1244  }
1245 
1246  for (auto targets_it = targets.cbegin(); targets_it != targets.cend(); ++targets_it) {
1247  for (auto ecus_it = targets_it->ecus().cbegin(); ecus_it != targets_it->ecus().cend(); ++ecus_it) {
1248  const Uptane::EcuSerial ecu_serial = ecus_it->first;
1249 
1250  auto sec = secondaries.find(ecu_serial);
1251  if (sec != secondaries.end()) {
1252  /* Root rotation if necessary */
1253  rotateSecondaryRoot(Uptane::RepositoryType::Director(), *(sec->second));
1254  rotateSecondaryRoot(Uptane::RepositoryType::Image(), *(sec->second));
1255  if (!sec->second->putMetadata(meta)) {
1256  LOG_ERROR << "Sending metadata to " << sec->first << " failed";
1257  }
1258  }
1259  }
1260  }
1261 }
1262 
1263 std::future<data::ResultCode::Numeric> SotaUptaneClient::sendFirmwareAsync(Uptane::SecondaryInterface &secondary,
1264  const Uptane::Target &target) {
1265  auto f = [this, &secondary, target]() {
1266  const std::string &correlation_id = director_repo.getCorrelationId();
1267 
1268  sendEvent<event::InstallStarted>(secondary.getSerial());
1269  report_queue->enqueue(std_::make_unique<EcuInstallationStartedReport>(secondary.getSerial(), correlation_id));
1270 
1271  std::string data_to_send;
1272  bool send_firmware_result = false;
1273 
1274  if (target.IsOstree()) {
1275  // empty firmware means OSTree secondaries: pack credentials instead
1276  data_to_send = secondaryTreehubCredentials();
1277  } else {
1278  std::stringstream sstr;
1279  sstr << *storage->openTargetFile(target);
1280  data_to_send = sstr.str();
1281  }
1282 
1283  if (!data_to_send.empty()) {
1284  send_firmware_result = secondary.sendFirmware(data_to_send);
1285  }
1286 
1288  send_firmware_result ? data::ResultCode::Numeric::kOk : data::ResultCode::Numeric::kInstallFailed;
1289 
1290  if (send_firmware_result) {
1291  result = secondary.install(target.filename());
1292  }
1293 
1294  if (result == data::ResultCode::Numeric::kNeedCompletion) {
1295  report_queue->enqueue(std_::make_unique<EcuInstallationAppliedReport>(secondary.getSerial(), correlation_id));
1296  } else {
1297  report_queue->enqueue(std_::make_unique<EcuInstallationCompletedReport>(
1298  secondary.getSerial(), correlation_id, result == data::ResultCode::Numeric::kOk));
1299  }
1300 
1301  sendEvent<event::InstallTargetComplete>(secondary.getSerial(), result == data::ResultCode::Numeric::kOk);
1302  return result;
1303  };
1304 
1305  return std::async(std::launch::async, f);
1306 }
1307 
1308 std::vector<result::Install::EcuReport> SotaUptaneClient::sendImagesToEcus(const std::vector<Uptane::Target> &targets) {
1309  std::vector<result::Install::EcuReport> reports;
1310  std::vector<std::pair<result::Install::EcuReport, std::future<data::ResultCode::Numeric>>> firmwareFutures;
1311 
1312  const Uptane::EcuSerial &primary_ecu_serial = primaryEcuSerial();
1313  // target images should already have been downloaded to metadata_path/targets/
1314  for (auto targets_it = targets.cbegin(); targets_it != targets.cend(); ++targets_it) {
1315  for (auto ecus_it = targets_it->ecus().cbegin(); ecus_it != targets_it->ecus().cend(); ++ecus_it) {
1316  const Uptane::EcuSerial &ecu_serial = ecus_it->first;
1317 
1318  if (primary_ecu_serial == ecu_serial) {
1319  continue;
1320  }
1321 
1322  auto f = secondaries.find(ecu_serial);
1323  if (f == secondaries.end()) {
1324  LOG_ERROR << "Target " << *targets_it << " has unknown ECU ID";
1325  last_exception = Uptane::BadEcuId(targets_it->filename());
1326  continue;
1327  }
1328 
1329  Uptane::SecondaryInterface &sec = *f->second;
1330  firmwareFutures.emplace_back(result::Install::EcuReport(*targets_it, ecu_serial, data::InstallationResult()),
1331  sendFirmwareAsync(sec, *targets_it));
1332  }
1333  }
1334 
1335  for (auto &f : firmwareFutures) {
1336  data::ResultCode::Numeric fut_result = data::ResultCode::Numeric::kUnknown;
1337 
1338  fut_result = f.second.get();
1339  f.first.install_res = data::InstallationResult(fut_result, "");
1340 
1341  if (fiu_fail((std::string("secondary_install_") + f.first.serial.ToString()).c_str()) != 0) {
1342  // consider changing this approach of the fault injection, since the current approach impacts the non-test code
1343  // flow here as well as it doesn't test the installation failure on secondary from an end-to-end perspective as it
1344  // injects an error on the middle of the control flow that would have happened if an installation error had
1345  // happened in case of the virtual or the ip-secondary or any other secondary, e.g. add a mock secondary that
1346  // returns an error to sendFirmware/install request we might consider passing the installation description message
1347  // from Secondary, not just bool and/or data::ResultCode::Numeric
1348 
1349  // fault injection
1351  f.first.install_res = data::InstallationResult(data::ResultCode(fut_result, fault_injection_last_info()), "");
1352  }
1353 
1354  if (fut_result == data::ResultCode::Numeric::kOk || fut_result == data::ResultCode::Numeric::kNeedCompletion) {
1355  f.first.update.setCorrelationId(director_repo.getCorrelationId());
1356  auto update_mode = fut_result == data::ResultCode::Numeric::kOk ? InstalledVersionUpdateMode::kCurrent
1357  : InstalledVersionUpdateMode::kPending;
1358  storage->saveInstalledVersion(f.first.serial.ToString(), f.first.update, update_mode);
1359  }
1360 
1361  storage->saveEcuInstallationResult(f.first.serial, f.first.install_res);
1362  reports.push_back(f.first);
1363  }
1364  return reports;
1365 }
1366 
1367 std::string SotaUptaneClient::secondaryTreehubCredentials() const {
1368  if (config.tls.pkey_source != CryptoSource::kFile || config.tls.cert_source != CryptoSource::kFile ||
1369  config.tls.ca_source != CryptoSource::kFile) {
1370  LOG_ERROR << "Cannot send OSTree update to a secondary when not using file as credential sources";
1371  return "";
1372  }
1373  std::string ca, cert, pkey;
1374  if (!storage->loadTlsCreds(&ca, &cert, &pkey)) {
1375  LOG_ERROR << "Could not load tls credentials from storage";
1376  return "";
1377  }
1378 
1379  std::string treehub_url = config.pacman.ostree_server;
1380  std::map<std::string, std::string> archive_map = {
1381  {"ca.pem", ca}, {"client.pem", cert}, {"pkey.pem", pkey}, {"server.url", treehub_url}};
1382 
1383  try {
1384  std::stringstream as;
1385  Utils::writeArchive(archive_map, as);
1386 
1387  return as.str();
1388  } catch (std::runtime_error &exc) {
1389  LOG_ERROR << "Could not create credentials archive: " << exc.what();
1390  return "";
1391  }
1392 }
1393 
1394 Uptane::LazyTargetsList SotaUptaneClient::allTargets() const {
1395  return Uptane::LazyTargetsList(images_repo, storage, uptane_fetcher);
1396 }
1397 
1398 void SotaUptaneClient::checkAndUpdatePendingSecondaries() {
1399  // TODO: think of another alternatives to inform Primary about installation result on Secondary ECUs (reboot case)
1400  std::vector<std::pair<Uptane::EcuSerial, Uptane::Hash>> pending_ecus;
1401  storage->getPendingEcus(&pending_ecus);
1402 
1403  for (const auto &pending_ecu : pending_ecus) {
1404  if (primaryEcuSerial() == pending_ecu.first) {
1405  continue;
1406  }
1407  auto &sec = secondaries[pending_ecu.first];
1408  const auto &manifest = sec->getManifest();
1409  if (!manifest.verifySignature(sec->getPublicKey())) {
1410  LOG_ERROR << "Invalid signature of the manifest reported by secondary: "
1411  << " serial: " << pending_ecu.first << " manifest: " << manifest;
1412  // what should we do in this case ?
1413  continue;
1414  }
1415  auto current_ecu_hash = manifest.installedImageHash();
1416  if (pending_ecu.second == current_ecu_hash) {
1417  LOG_INFO << "The pending update " << current_ecu_hash << " has been installed on " << pending_ecu.first;
1418  boost::optional<Uptane::Target> pending_version;
1419  if (storage->loadInstalledVersions(pending_ecu.first.ToString(), nullptr, &pending_version)) {
1420  storage->saveEcuInstallationResult(pending_ecu.first,
1421  data::InstallationResult(data::ResultCode::Numeric::kOk, ""));
1422 
1423  storage->saveInstalledVersion(pending_ecu.first.ToString(), *pending_version,
1424  InstalledVersionUpdateMode::kCurrent);
1425 
1426  report_queue->enqueue(std_::make_unique<EcuInstallationCompletedReport>(
1427  pending_ecu.first, pending_version->correlation_id(), true));
1428 
1430  std::string raw_report;
1431  computeDeviceInstallationResult(&ir, &raw_report);
1432  storage->storeDeviceInstallationResult(ir, raw_report, pending_version->correlation_id());
1433  }
1434  }
1435  }
1436 }
1437 
1438 boost::optional<Uptane::HardwareIdentifier> SotaUptaneClient::ecuHwId(const Uptane::EcuSerial &serial) const {
1439  if (serial == primary_ecu_serial_ || serial.ToString().empty()) {
1440  if (primary_ecu_hw_id_ == Uptane::HardwareIdentifier::Unknown()) {
1441  return boost::none;
1442  }
1443  return primary_ecu_hw_id_;
1444  }
1445 
1446  const auto it = secondaries.find(serial);
1447  if (it != secondaries.end()) {
1448  return it->second->getHwId();
1449  }
1450 
1451  return boost::none;
1452 }
data::ResultCode
Definition: types.h:125
Uptane::BadHardwareId
Definition: exceptions.h:101
data::ResultCode::Numeric::kVerificationFailed
Metadata verification failed.
KeyManager
Definition: keymanager.h:13
data::ResultCode::Numeric::kAlreadyProcessed
Operation has already been processed.
Uptane::InvalidTarget
Definition: exceptions.h:130
data::InstallationResult
Definition: types.h:182
Uptane::Version
Metadata version numbers.
Definition: tuf.h:116
TelemetryConfig::report_network
bool report_network
Report device network information: IP address, hostname, MAC address.
Definition: telemetryconfig.h:10
Uptane::BadEcuId
Definition: exceptions.h:94
result::UpdateCheck
Container for information about available updates.
Definition: results.h:38
Uptane::HardwareIdentifier
Definition: tuf.h:143
HttpResponse
Definition: httpinterface.h:17
Uptane::RawMetaPack
Definition: tuf.h:534
Uptane::TargetMismatch
Definition: exceptions.h:74
Uptane::RepositoryType
Definition: tuf.h:20
result::UpdateStatus
UpdateStatus
Status of an update.
Definition: results.h:26
result::Install::EcuReport
Definition: results.h:143
Uptane::EcuSerial
Definition: tuf.h:174
Uptane::Targets
Definition: tuf.h:439
SecondaryInfo
Definition: invstorage.h:106
result::Download
Container for information about downloading an update.
Definition: results.h:117
PublicKey
Definition: crypto.h:26
Uptane::Exception
Definition: exceptions.h:10
Uptane::LazyTargetsList
Definition: iterator.h:12
api::FlowControlToken
Provides a thread-safe way to pause and terminate task execution.
Definition: apiqueue.h:19
SerialCompare
Definition: sotauptaneclient.h:192
Uptane::Target::IsOstree
bool IsOstree() const
Is this an OSTree target? OSTree targets need special treatment because the hash doesn't represent th...
Definition: tuf.cc:220
result
Results of libaktualizr API calls.
Definition: results.h:13
Initializer
Definition: initializer.h:13
result::Install
Container for information about installing an update.
Definition: results.h:130
data::ResultCode::Numeric::kInternalError
SWM Internal integrity error.
Uptane::Role
TUF Roles.
Definition: tuf.h:57
Uptane::Target
Definition: tuf.h:238
data::ResultCode::Numeric
Numeric
Definition: types.h:128
result::CampaignCheck
Container for information about available campaigns.
Definition: results.h:17
data::ResultCode::Numeric::kDownloadFailed
Package download failed.
data::ResultCode::Numeric::kInstallFailed
Package installation failed.
TargetCompare
Definition: sotauptaneclient.h:183
Uptane::SecondaryInterface
Definition: secondaryinterface.h:12
Uptane::Manifest
Definition: manifest.h:13
event
Aktualizr status events.
Definition: events.h:18