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::updateImageMeta() {
344  if (!image_repo.updateMeta(*storage, *uptane_fetcher)) {
345  last_exception = image_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::checkImageMetaOffline() {
360  if (!image_repo.checkMetaOffline(*storage)) {
361  last_exception = image_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  // 5.4.4.6.8. If checking Targets metadata from the Director repository,
455  // and the ECU performing the verification is the Primary ECU, check that
456  // all listed ECU identifiers correspond to ECUs that are actually present
457  // in the vehicle.
458  auto hw_id_known = ecuHwId(ecu_serial);
459  if (!hw_id_known) {
460  LOG_ERROR << "Unknown ECU ID in Director Targets metadata: " << ecu_serial.ToString();
461  last_exception = Uptane::BadEcuId(target.filename());
462  return false;
463  }
464 
465  if (*hw_id_known != hw_id) {
466  LOG_ERROR << "Wrong hardware identifier for ECU " << ecu_serial.ToString();
467  last_exception = Uptane::BadHardwareId(target.filename());
468  return false;
469  }
470 
471  boost::optional<Uptane::Target> current_version;
472  if (!storage->loadInstalledVersions(ecu_serial.ToString(), &current_version, nullptr)) {
473  LOG_WARNING << "Could not load currently installed version for ECU ID: " << ecu_serial.ToString();
474  break;
475  }
476 
477  if (!current_version) {
478  LOG_WARNING << "Current version for ECU ID: " << ecu_serial.ToString() << " is unknown";
479  is_new = true;
480  } else if (current_version->filename() != target.filename()) {
481  is_new = true;
482  }
483 
484  if (primary_ecu_serial == ecu_serial) {
485  if (!target.IsOstree() &&
486  (config.pacman.type == PACKAGE_MANAGER_OSTREE || config.pacman.type == PACKAGE_MANAGER_OSTREEDOCKERAPP)) {
487  LOG_ERROR << "Cannot install a non-OSTree package on an OSTree system";
488  last_exception = Uptane::InvalidTarget(target.filename());
489  return false;
490  }
491  }
492 
493  if (is_new && ecus_count != nullptr) {
494  (*ecus_count)++;
495  }
496  // no updates for this image => continue
497  }
498  if (is_new) {
499  new_targets->push_back(target);
500  }
501  }
502  return true;
503 }
504 
505 std::unique_ptr<Uptane::Target> SotaUptaneClient::findTargetHelper(const Uptane::Targets &cur_targets,
506  const Uptane::Target &queried_target,
507  const int level, const bool terminating,
508  const bool offline) {
509  TargetCompare target_comp(queried_target);
510  const auto it = std::find_if(cur_targets.targets.cbegin(), cur_targets.targets.cend(), target_comp);
511  if (it != cur_targets.targets.cend()) {
512  return std_::make_unique<Uptane::Target>(*it);
513  }
514 
515  if (terminating || level >= Uptane::kDelegationsMaxDepth) {
516  return std::unique_ptr<Uptane::Target>(nullptr);
517  }
518 
519  for (const auto &delegate_name : cur_targets.delegated_role_names_) {
520  Uptane::Role delegate_role = Uptane::Role::Delegation(delegate_name);
521  auto patterns = cur_targets.paths_for_role_.find(delegate_role);
522  if (patterns == cur_targets.paths_for_role_.end()) {
523  continue;
524  }
525 
526  bool match = false;
527  for (const auto &pattern : patterns->second) {
528  if (fnmatch(pattern.c_str(), queried_target.filename().c_str(), 0) == 0) {
529  match = true;
530  break;
531  }
532  }
533  if (!match) {
534  continue;
535  }
536 
537  // Target name matches one of the patterns
538 
539  auto delegation =
540  Uptane::getTrustedDelegation(delegate_role, cur_targets, image_repo, *storage, *uptane_fetcher, offline);
541  if (delegation.isExpired(TimeStamp::Now())) {
542  continue;
543  }
544 
545  auto is_terminating = cur_targets.terminating_role_.find(delegate_role);
546  if (is_terminating == cur_targets.terminating_role_.end()) {
547  throw Uptane::Exception("image", "Inconsistent delegations");
548  }
549 
550  auto found_target = findTargetHelper(delegation, queried_target, level + 1, is_terminating->second, offline);
551  if (found_target != nullptr) {
552  return found_target;
553  }
554  }
555 
556  return std::unique_ptr<Uptane::Target>(nullptr);
557 }
558 
559 std::unique_ptr<Uptane::Target> SotaUptaneClient::findTargetInDelegationTree(const Uptane::Target &target,
560  const bool offline) {
561  auto toplevel_targets = image_repo.getTargets();
562  if (toplevel_targets == nullptr) {
563  return std::unique_ptr<Uptane::Target>(nullptr);
564  }
565 
566  return findTargetHelper(*toplevel_targets, target, 0, false, offline);
567 }
568 
569 result::Download SotaUptaneClient::downloadImages(const std::vector<Uptane::Target> &targets,
570  const api::FlowControlToken *token) {
571  // Uptane step 4 - download all the images and verify them against the metadata (for OSTree - pull without
572  // deploying)
573  std::lock_guard<std::mutex> guard(download_mutex);
575  std::vector<Uptane::Target> downloaded_targets;
576 
577  result::UpdateStatus update_status = checkUpdatesOffline(targets);
578  if (update_status != result::UpdateStatus::kUpdatesAvailable) {
579  if (update_status == result::UpdateStatus::kNoUpdatesAvailable) {
580  result = result::Download({}, result::DownloadStatus::kNothingToDownload, "");
581  } else {
582  result =
583  result::Download(downloaded_targets, result::DownloadStatus::kError, "Error rechecking stored metadata.");
584  storeInstallationFailure(
585  data::InstallationResult(data::ResultCode::Numeric::kInternalError, "Error rechecking stored metadata."));
586  }
587  sendEvent<event::AllDownloadsComplete>(result);
588  return result;
589  }
590 
591  for (const auto &target : targets) {
592  auto res = downloadImage(target, token);
593  if (res.first) {
594  downloaded_targets.push_back(res.second);
595  }
596  }
597 
598  if (targets.size() == downloaded_targets.size()) {
599  result = result::Download(downloaded_targets, result::DownloadStatus::kSuccess, "");
600  } else {
601  if (downloaded_targets.size() == 0) {
602  LOG_ERROR << "0 of " << targets.size() << " targets were successfully downloaded.";
603  result = result::Download(downloaded_targets, result::DownloadStatus::kError, "Each target download has failed");
604  } else {
605  LOG_ERROR << "Only " << downloaded_targets.size() << " of " << targets.size() << " were successfully downloaded.";
606  result = result::Download(downloaded_targets, result::DownloadStatus::kPartialSuccess, "");
607  }
608  storeInstallationFailure(
610  }
611 
612  sendEvent<event::AllDownloadsComplete>(result);
613  return result;
614 }
615 
616 void SotaUptaneClient::reportPause() {
617  const std::string &correlation_id = director_repo.getCorrelationId();
618  report_queue->enqueue(std_::make_unique<DevicePausedReport>(correlation_id));
619 }
620 
621 void SotaUptaneClient::reportResume() {
622  const std::string &correlation_id = director_repo.getCorrelationId();
623  report_queue->enqueue(std_::make_unique<DeviceResumedReport>(correlation_id));
624 }
625 
626 std::pair<bool, Uptane::Target> SotaUptaneClient::downloadImage(const Uptane::Target &target,
627  const api::FlowControlToken *token) {
628  // TODO: support downloading encrypted targets from director
629 
630  const std::string &correlation_id = director_repo.getCorrelationId();
631  // send an event for all ecus that are touched by this target
632  for (const auto &ecu : target.ecus()) {
633  report_queue->enqueue(std_::make_unique<EcuDownloadStartedReport>(ecu.first, correlation_id));
634  }
635 
636  KeyManager keys(storage, config.keymanagerConfig());
637  keys.loadKeys();
638  auto prog_cb = [this](const Uptane::Target &t, const std::string &description, unsigned int progress) {
639  report_progress_cb(events_channel.get(), t, description, progress);
640  };
641 
642  bool success = false;
643  const Uptane::EcuSerial &primary_ecu_serial = primaryEcuSerial();
644 
645  if (target.IsForEcu(primary_ecu_serial) || !target.IsOstree()) {
646  // TODO: download should be the logical ECU and packman specific
647  const int max_tries = 3;
648  int tries = 0;
649  std::chrono::milliseconds wait(500);
650 
651  for (; tries < max_tries; tries++) {
652  success = package_manager_->fetchTarget(target, *uptane_fetcher, keys, prog_cb, token);
653  // Skip trying to fetch the 'target' if control flow token transaction
654  // was set to the 'abort' or 'pause' state, see the CommandQueue and FlowControlToken.
655  if (success || (token != nullptr && !token->canContinue(false))) {
656  break;
657  } else if (tries < max_tries - 1) {
658  std::this_thread::sleep_for(wait);
659  wait *= 2;
660  }
661  }
662  if (!success) {
663  LOG_ERROR << "Download unsuccessful after " << tries << " attempts.";
664  }
665  } else {
666  // we emulate successfull download in case of the secondary ostree update
667  success = true;
668  }
669 
670  // send this asynchronously before `sendEvent`, so that the report timestamp
671  // would not be delayed by callbacks on events
672  for (const auto &ecu : target.ecus()) {
673  report_queue->enqueue(std_::make_unique<EcuDownloadCompletedReport>(ecu.first, correlation_id, success));
674  }
675 
676  sendEvent<event::DownloadTargetComplete>(target, success);
677  return {success, target};
678 }
679 
680 bool SotaUptaneClient::uptaneIteration(std::vector<Uptane::Target> *targets, unsigned int *ecus_count) {
681  if (!updateDirectorMeta()) {
682  LOG_ERROR << "Failed to update Director metadata: " << last_exception.what();
683  return false;
684  }
685  std::vector<Uptane::Target> tmp_targets;
686  unsigned int ecus;
687  if (!getNewTargets(&tmp_targets, &ecus)) {
688  LOG_ERROR << "Inconsistency between Director metadata and discovered ECUs";
689  return false;
690  }
691 
692  if (tmp_targets.empty()) {
693  if (targets != nullptr) {
694  *targets = std::move(tmp_targets);
695  }
696  if (ecus_count != nullptr) {
697  *ecus_count = ecus;
698  }
699  return true;
700  }
701 
702  LOG_INFO << "New updates found in Director metadata. Checking Image repo metadata...";
703 
704  if (!updateImageMeta()) {
705  LOG_ERROR << "Failed to update Image repo metadata: " << last_exception.what();
706  return false;
707  }
708 
709  if (targets != nullptr) {
710  *targets = std::move(tmp_targets);
711  }
712  if (ecus_count != nullptr) {
713  *ecus_count = ecus;
714  }
715  return true;
716 }
717 
718 bool SotaUptaneClient::uptaneOfflineIteration(std::vector<Uptane::Target> *targets, unsigned int *ecus_count) {
719  if (!checkDirectorMetaOffline()) {
720  LOG_ERROR << "Failed to check Director metadata: " << last_exception.what();
721  return false;
722  }
723  std::vector<Uptane::Target> tmp_targets;
724  unsigned int ecus;
725  if (!getNewTargets(&tmp_targets, &ecus)) {
726  LOG_ERROR << "Inconsistency between Director metadata and existent ECUs";
727  return false;
728  }
729 
730  if (tmp_targets.empty()) {
731  *targets = std::move(tmp_targets);
732  if (ecus_count != nullptr) {
733  *ecus_count = ecus;
734  }
735  return true;
736  }
737 
738  if (!checkImageMetaOffline()) {
739  LOG_ERROR << "Failed to check Image repo metadata: " << last_exception.what();
740  return false;
741  }
742 
743  *targets = std::move(tmp_targets);
744  if (ecus_count != nullptr) {
745  *ecus_count = ecus;
746  }
747  return true;
748 }
749 
750 void SotaUptaneClient::sendDeviceData() {
751  reportHwInfo();
752  reportInstalledPackages();
753  reportNetworkInfo();
754  reportAktualizrConfiguration();
755  putManifestSimple();
756  sendEvent<event::SendDeviceDataComplete>();
757 }
758 
759 result::UpdateCheck SotaUptaneClient::fetchMeta() {
761 
762  reportNetworkInfo();
763 
764  if (hasPendingUpdates()) {
765  // if there are some pending updates check if the secondaries' pending updates have been applied
766  LOG_INFO << "The current update is pending. Check if pending ECUs has been already updated";
767  checkAndUpdatePendingSecondaries();
768  }
769 
770  if (hasPendingUpdates()) {
771  // if there are still some pending updates just return, don't check for new updates
772  // no need in update checking if there are some pending updates
773  LOG_INFO << "An update is pending. Skipping check for update until installation is complete.";
774  return result::UpdateCheck({}, 0, result::UpdateStatus::kError, Json::nullValue,
775  "There are pending updates, no new updates are checked");
776  }
777 
778  // Uptane step 1 (build the vehicle version manifest):
779  if (!putManifestSimple()) {
780  LOG_ERROR << "Error sending manifest!";
781  }
782  result = checkUpdates();
783  sendEvent<event::UpdateCheckComplete>(result);
784 
785  return result;
786 }
787 
788 result::UpdateCheck SotaUptaneClient::checkUpdates() {
790 
791  std::vector<Uptane::Target> updates;
792  unsigned int ecus_count = 0;
793  if (!uptaneIteration(&updates, &ecus_count)) {
794  result = result::UpdateCheck({}, 0, result::UpdateStatus::kError, Json::nullValue, "Could not update metadata.");
795  return result;
796  }
797 
798  std::string director_targets;
799  storage->loadNonRoot(&director_targets, Uptane::RepositoryType::Director(), Uptane::Role::Targets());
800 
801  if (updates.empty()) {
802  LOG_DEBUG << "No new updates found in Uptane metadata.";
803  result =
804  result::UpdateCheck({}, 0, result::UpdateStatus::kNoUpdatesAvailable, Utils::parseJSON(director_targets), "");
805  return result;
806  }
807 
808  // 5.4.4.2.10.: Verify that Targets metadata from the Director and Image
809  // repositories match. A Primary ECU MUST perform this check on metadata for
810  // all images listed in the Targets metadata file from the Director
811  // repository.
812  for (auto &target : updates) {
813  auto image_target = findTargetInDelegationTree(target, false);
814  if (image_target == nullptr) {
815  // TODO: Could also be a missing target or delegation expiration.
816  last_exception = Uptane::TargetMismatch(target.filename());
817  LOG_ERROR << "No matching target in Image repo Targets metadata for " << target;
818  result = result::UpdateCheck({}, 0, result::UpdateStatus::kError, Utils::parseJSON(director_targets),
819  "Target mismatch.");
820  storeInstallationFailure(
822  return result;
823  }
824  // If the URL from the Director is unset, but the URL from the Image repo
825  // is set, use that.
826  if (target.uri().empty() && !image_target->uri().empty()) {
827  target.setUri(image_target->uri());
828  }
829  }
830 
831  result = result::UpdateCheck(updates, ecus_count, result::UpdateStatus::kUpdatesAvailable,
832  Utils::parseJSON(director_targets), "");
833  if (updates.size() == 1) {
834  LOG_INFO << "1 new update found in both Director and Image repo metadata.";
835  } else {
836  LOG_INFO << updates.size() << " new updates found in both Director and Image repo metadata.";
837  }
838  return result;
839 }
840 
841 result::UpdateStatus SotaUptaneClient::checkUpdatesOffline(const std::vector<Uptane::Target> &targets) {
842  if (hasPendingUpdates()) {
843  // no need in update checking if there are some pending updates
844  LOG_INFO << "An update is pending. Skipping stored metadata check until installation is complete.";
845  return result::UpdateStatus::kError;
846  }
847 
848  if (targets.empty()) {
849  LOG_WARNING << "Requested targets vector is empty. Nothing to do.";
850  return result::UpdateStatus::kError;
851  }
852 
853  std::vector<Uptane::Target> director_targets;
854  unsigned int ecus_count = 0;
855  if (!uptaneOfflineIteration(&director_targets, &ecus_count)) {
856  LOG_ERROR << "Invalid Uptane metadata in storage.";
857  return result::UpdateStatus::kError;
858  }
859 
860  if (director_targets.empty()) {
861  LOG_ERROR << "No new updates found in Uptane metadata, but expected " << targets.size() << ".";
862  return result::UpdateStatus::kNoUpdatesAvailable;
863  }
864 
865  // For every target in the Director Targets metadata, walk the delegation
866  // tree (if necessary) and find a matching target in the Image repo
867  // metadata.
868  for (const auto &target : targets) {
869  TargetCompare target_comp(target);
870  const auto it = std::find_if(director_targets.cbegin(), director_targets.cend(), target_comp);
871  if (it == director_targets.cend()) {
872  LOG_ERROR << "No matching target in Director Targets metadata for " << target;
873  return result::UpdateStatus::kError;
874  }
875 
876  const auto image_target = findTargetInDelegationTree(target, true);
877  if (image_target == nullptr) {
878  LOG_ERROR << "No matching target in Image repo Targets metadata for " << target;
879  return result::UpdateStatus::kError;
880  }
881  }
882 
883  return result::UpdateStatus::kUpdatesAvailable;
884 }
885 
886 result::Install SotaUptaneClient::uptaneInstall(const std::vector<Uptane::Target> &updates) {
887  const std::string &correlation_id = director_repo.getCorrelationId();
888 
889  // clear all old results first
890  storage->clearInstallationResults();
891 
892  // put most of the logic in a lambda so that we can take care of common
893  // post-operations
894  result::Install r;
895  std::string raw_report;
896  bool drop_targets = false;
897 
898  std::tie(r, raw_report, drop_targets) = [this, &updates,
899  &correlation_id]() -> std::tuple<result::Install, std::string, bool> {
901 
902  // Recheck the Uptane metadata and make sure the requested updates are
903  // consistent with the stored metadata.
904  result::UpdateStatus update_status = checkUpdatesOffline(updates);
905  if (update_status != result::UpdateStatus::kUpdatesAvailable) {
906  if (update_status == result::UpdateStatus::kNoUpdatesAvailable) {
907  result.dev_report = {false, data::ResultCode::Numeric::kAlreadyProcessed, ""};
908  } else {
909  result.dev_report = {false, data::ResultCode::Numeric::kInternalError, ""};
910  }
911  return std::make_tuple(result, "Stored Uptane metadata is invalid", false);
912  }
913 
914  Uptane::EcuSerial primary_ecu_serial = primaryEcuSerial();
915  // Recheck the downloaded update hashes.
916  for (const auto &update : updates) {
917  if (update.IsForEcu(primary_ecu_serial) || !update.IsOstree()) {
918  // download binary images for any target, for both Primary and Secondary
919  // download an ostree revision just for Primary, Secondary will do it by itself
920  // Primary cannot verify downloaded OSTree targets for Secondaries,
921  // Downloading of Secondary's ostree repo revision to the Primary's can fail
922  // if they differ signficantly as ostree has a certain cap/limit of the diff it pulls
923  if (package_manager_->verifyTarget(update) != TargetStatus::kGood) {
924  result.dev_report = {false, data::ResultCode::Numeric::kInternalError, ""};
925  return std::make_tuple(result, "Downloaded target is invalid", false);
926  }
927  }
928  }
929 
930  // wait some time for secondaries to come up
931  // note: this fail after a time out but will be retried at the next install
932  // phase if the targets have not been changed. This is done to avoid being
933  // stuck in an unrecoverable state here
934  if (!waitSecondariesReachable(updates)) {
935  result.dev_report = {false, data::ResultCode::Numeric::kInternalError, "Unreachable secondary"};
936  return std::make_tuple(result, "Secondaries were not available", false);
937  }
938 
939  // Uptane step 5 (send time to all ECUs) is not implemented yet.
940  std::vector<Uptane::Target> primary_updates = findForEcu(updates, primary_ecu_serial);
941 
942  // 6 - send metadata to all the ECUs
943  if (!sendMetadataToEcus(updates)) {
944  result.dev_report = {false, data::ResultCode::Numeric::kInternalError, "Metadata verification failed"};
945  return std::make_tuple(result, "Secondary metadata verification failed", true);
946  }
947 
948  // 7 - send images to ECUs (deploy for OSTree)
949  if (primary_updates.size() != 0u) {
950  // assuming one OSTree OS per primary => there can be only one OSTree update
951  Uptane::Target primary_update = primary_updates[0];
952  primary_update.setCorrelationId(correlation_id);
953 
954  report_queue->enqueue(std_::make_unique<EcuInstallationStartedReport>(primary_ecu_serial, correlation_id));
955  sendEvent<event::InstallStarted>(primary_ecu_serial);
956 
957  data::InstallationResult install_res;
958  if (!isInstalledOnPrimary(primary_update)) {
959  // notify the bootloader before installation happens, because installation is not atomic and
960  // a false notification doesn't hurt when rollbacks are implemented
961  package_manager_->updateNotify();
962  install_res = PackageInstallSetResult(primary_update);
963  if (install_res.result_code.num_code == data::ResultCode::Numeric::kNeedCompletion) {
964  // update needs a reboot, send distinct EcuInstallationApplied event
965  report_queue->enqueue(std_::make_unique<EcuInstallationAppliedReport>(primary_ecu_serial, correlation_id));
966  sendEvent<event::InstallTargetComplete>(primary_ecu_serial, true);
967  } else if (install_res.result_code.num_code == data::ResultCode::Numeric::kOk) {
968  storage->saveEcuInstallationResult(primary_ecu_serial, install_res);
969  report_queue->enqueue(
970  std_::make_unique<EcuInstallationCompletedReport>(primary_ecu_serial, correlation_id, true));
971  sendEvent<event::InstallTargetComplete>(primary_ecu_serial, true);
972  } else {
973  // general error case
974  storage->saveEcuInstallationResult(primary_ecu_serial, install_res);
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  } else {
980  install_res =
982  storage->saveEcuInstallationResult(primary_ecu_serial, install_res);
983  // TODO: distinguish this case from regular failure for local and remote event reporting
984  report_queue->enqueue(
985  std_::make_unique<EcuInstallationCompletedReport>(primary_ecu_serial, correlation_id, false));
986  sendEvent<event::InstallTargetComplete>(primary_ecu_serial, false);
987  }
988  result.ecu_reports.emplace(result.ecu_reports.begin(), primary_update, primary_ecu_serial, install_res);
989  } else {
990  LOG_INFO << "No update to install on primary";
991  }
992 
993  auto sec_reports = sendImagesToEcus(updates);
994  result.ecu_reports.insert(result.ecu_reports.end(), sec_reports.begin(), sec_reports.end());
995  std::string rr;
996  computeDeviceInstallationResult(&result.dev_report, &rr);
997 
998  return std::make_tuple(result, rr, !(result.dev_report.isSuccess() || result.dev_report.needCompletion()));
999  }();
1000 
1001  // TODO(OTA-2178): think of exception handling; the SQLite related code can throw exceptions
1002  storage->storeDeviceInstallationResult(r.dev_report, raw_report, correlation_id);
1003 
1004  sendEvent<event::AllInstallsComplete>(r);
1005 
1006  if (drop_targets) {
1007  director_repo.dropTargets(*storage); // fix for OTA-2587, listen to backend again after end of install
1008  }
1009 
1010  return r;
1011 }
1012 
1013 result::CampaignCheck SotaUptaneClient::campaignCheck() {
1014  auto campaigns = campaign::Campaign::fetchAvailableCampaigns(*http, config.tls.server);
1015  for (const auto &c : campaigns) {
1016  LOG_INFO << "Campaign: " << c.name;
1017  LOG_INFO << "Campaign id: " << c.id;
1018  LOG_INFO << "Campaign size: " << c.size;
1019  LOG_INFO << "CampaignAccept required: " << (c.autoAccept ? "no" : "yes");
1020  LOG_INFO << "Message: " << c.description;
1021  }
1022  result::CampaignCheck result(campaigns);
1023  sendEvent<event::CampaignCheckComplete>(result);
1024  return result;
1025 }
1026 
1027 void SotaUptaneClient::campaignAccept(const std::string &campaign_id) {
1028  sendEvent<event::CampaignAcceptComplete>();
1029  report_queue->enqueue(std_::make_unique<CampaignAcceptedReport>(campaign_id));
1030 }
1031 
1032 void SotaUptaneClient::campaignDecline(const std::string &campaign_id) {
1033  sendEvent<event::CampaignDeclineComplete>();
1034  report_queue->enqueue(std_::make_unique<CampaignDeclinedReport>(campaign_id));
1035 }
1036 
1037 void SotaUptaneClient::campaignPostpone(const std::string &campaign_id) {
1038  sendEvent<event::CampaignPostponeComplete>();
1039  report_queue->enqueue(std_::make_unique<CampaignPostponedReport>(campaign_id));
1040 }
1041 
1042 bool SotaUptaneClient::isInstallCompletionRequired() const {
1043  std::vector<std::pair<Uptane::EcuSerial, Uptane::Hash>> pending_ecus;
1044  storage->getPendingEcus(&pending_ecus);
1045  bool pending_for_ecu = std::find_if(pending_ecus.begin(), pending_ecus.end(),
1046  [this](const std::pair<Uptane::EcuSerial, Uptane::Hash> &ecu) -> bool {
1047  return ecu.first == primary_ecu_serial_;
1048  }) != pending_ecus.end();
1049 
1050  return pending_for_ecu && config.uptane.force_install_completion;
1051 }
1052 
1053 void SotaUptaneClient::completeInstall() const {
1054  if (isInstallCompletionRequired()) {
1055  package_manager_->completeInstall();
1056  }
1057 }
1058 
1059 bool SotaUptaneClient::putManifestSimple(const Json::Value &custom) {
1060  // does not send event, so it can be used as a subset of other steps
1061  if (hasPendingUpdates()) {
1062  // Debug level here because info level is annoying if the update check
1063  // frequency is low.
1064  LOG_DEBUG << "An update is pending. Skipping manifest upload until installation is complete.";
1065  return false;
1066  }
1067 
1068  static bool connected = true;
1069  auto manifest = AssembleManifest();
1070  if (custom != Json::nullValue) {
1071  manifest["custom"] = custom;
1072  }
1073  auto signed_manifest = uptane_manifest->sign(manifest);
1074  HttpResponse response = http->put(config.uptane.director_server + "/manifest", signed_manifest);
1075  if (response.isOk()) {
1076  if (!connected) {
1077  LOG_INFO << "Connectivity is restored.";
1078  }
1079  connected = true;
1080  storage->clearInstallationResults();
1081  return true;
1082  } else {
1083  connected = false;
1084  }
1085 
1086  LOG_WARNING << "Put manifest request failed: " << response.getStatusStr();
1087  return false;
1088 }
1089 
1090 bool SotaUptaneClient::putManifest(const Json::Value &custom) {
1091  bool success = putManifestSimple(custom);
1092  sendEvent<event::PutManifestComplete>(success);
1093  return success;
1094 }
1095 
1096 // Check stored secondaries list against secondaries known to aktualizr.
1097 void SotaUptaneClient::verifySecondaries() {
1098  storage->clearMisconfiguredEcus();
1099  EcuSerials serials;
1100  if (!storage->loadEcuSerials(&serials) || serials.empty()) {
1101  LOG_ERROR << "No ECU serials found in storage!";
1102  return;
1103  }
1104 
1105  std::vector<MisconfiguredEcu> misconfigured_ecus;
1106  std::vector<bool> found(serials.size(), false);
1107  SerialCompare primary_comp(primaryEcuSerial());
1108  EcuSerials::const_iterator store_it;
1109  store_it = std::find_if(serials.cbegin(), serials.cend(), primary_comp);
1110  if (store_it == serials.cend()) {
1111  LOG_ERROR << "Primary ECU serial " << primaryEcuSerial() << " not found in storage!";
1112  misconfigured_ecus.emplace_back(primaryEcuSerial(), Uptane::HardwareIdentifier(""), EcuState::kOld);
1113  } else {
1114  found[static_cast<size_t>(std::distance(serials.cbegin(), store_it))] = true;
1115  }
1116 
1117  for (auto it = secondaries.cbegin(); it != secondaries.cend(); ++it) {
1118  SerialCompare secondary_comp(it->second->getSerial());
1119  store_it = std::find_if(serials.cbegin(), serials.cend(), secondary_comp);
1120  if (store_it == serials.cend()) {
1121  LOG_ERROR << "Secondary ECU serial " << it->second->getSerial() << " (hardware ID " << it->second->getHwId()
1122  << ") not found in storage!";
1123  misconfigured_ecus.emplace_back(it->second->getSerial(), it->second->getHwId(), EcuState::kNotRegistered);
1124  } else if (found[static_cast<size_t>(std::distance(serials.cbegin(), store_it))]) {
1125  LOG_ERROR << "Secondary ECU serial " << it->second->getSerial() << " (hardware ID " << it->second->getHwId()
1126  << ") has a duplicate entry in storage!";
1127  } else {
1128  found[static_cast<size_t>(std::distance(serials.cbegin(), store_it))] = true;
1129  }
1130  }
1131 
1132  std::vector<bool>::iterator found_it;
1133  for (found_it = found.begin(); found_it != found.end(); ++found_it) {
1134  if (!*found_it) {
1135  auto not_registered = serials[static_cast<size_t>(std::distance(found.begin(), found_it))];
1136  LOG_WARNING << "ECU serial " << not_registered.first << " in storage was not reported to aktualizr!";
1137  misconfigured_ecus.emplace_back(not_registered.first, not_registered.second, EcuState::kOld);
1138  }
1139  }
1140 
1141  storage->storeMisconfiguredEcus(misconfigured_ecus);
1142 }
1143 
1144 bool SotaUptaneClient::waitSecondariesReachable(const std::vector<Uptane::Target> &updates) {
1145  std::map<Uptane::EcuSerial, Uptane::SecondaryInterface *> targeted_secondaries;
1146  const Uptane::EcuSerial &primary_ecu_serial = primaryEcuSerial();
1147  for (const auto &t : updates) {
1148  for (const auto &ecu : t.ecus()) {
1149  if (ecu.first == primary_ecu_serial) {
1150  continue;
1151  }
1152  auto f = secondaries.find(ecu.first);
1153  if (f == secondaries.end()) {
1154  LOG_ERROR << "Target " << t << " has unknown ECU ID";
1155  continue;
1156  }
1157 
1158  targeted_secondaries[ecu.first] = f->second.get();
1159  }
1160  }
1161 
1162  if (targeted_secondaries.empty()) {
1163  return true;
1164  }
1165 
1166  LOG_INFO << "Waiting for secondaries to connect to start installation...";
1167 
1168  auto deadline = std::chrono::system_clock::now() + std::chrono::seconds(config.uptane.secondary_preinstall_wait_sec);
1169  while (std::chrono::system_clock::now() <= deadline) {
1170  if (targeted_secondaries.empty()) {
1171  return true;
1172  }
1173 
1174  for (auto sec_it = targeted_secondaries.begin(); sec_it != targeted_secondaries.end();) {
1175  if (sec_it->second->ping()) {
1176  sec_it = targeted_secondaries.erase(sec_it);
1177  } else {
1178  sec_it++;
1179  }
1180  }
1181  std::this_thread::sleep_for(std::chrono::seconds(1));
1182  }
1183 
1184  LOG_ERROR << "Secondaries did not connect: ";
1185 
1186  for (const auto &sec : targeted_secondaries) {
1187  LOG_ERROR << sec.second->getSerial();
1188  }
1189 
1190  return false;
1191 }
1192 
1193 void SotaUptaneClient::storeInstallationFailure(const data::InstallationResult &result) {
1194  // Store installation report to inform Director of the update failure before
1195  // we actually got to the install step.
1196  const std::string &correlation_id = director_repo.getCorrelationId();
1197  storage->storeDeviceInstallationResult(result, "", correlation_id);
1198  // Fix for OTA-2587, listen to backend again after end of install.
1199  director_repo.dropTargets(*storage);
1200 }
1201 
1202 void SotaUptaneClient::rotateSecondaryRoot(Uptane::RepositoryType repo, Uptane::SecondaryInterface &secondary) {
1203  std::string latest_root;
1204 
1205  if (!storage->loadLatestRoot(&latest_root, repo)) {
1206  LOG_ERROR << "No Root metadata to send";
1207  return;
1208  }
1209 
1210  int last_root_version = Uptane::extractVersionUntrusted(latest_root);
1211 
1212  int sec_root_version = secondary.getRootVersion((repo == Uptane::RepositoryType::Director()));
1213  if (sec_root_version >= 0) {
1214  for (int v = sec_root_version + 1; v <= last_root_version; v++) {
1215  std::string root;
1216  if (!storage->loadRoot(&root, repo, Uptane::Version(v))) {
1217  LOG_WARNING << "Couldn't find Root metadata in the storage, trying remote repo";
1218  if (!uptane_fetcher->fetchRole(&root, Uptane::kMaxRootSize, repo, Uptane::Role::Root(), Uptane::Version(v))) {
1219  // TODO(OTA-4552): looks problematic, robust procedure needs to be defined
1220  LOG_ERROR << "Root metadata could not be fetched, skipping to the next secondary";
1221  return;
1222  }
1223  }
1224  if (!secondary.putRoot(root, repo == Uptane::RepositoryType::Director())) {
1225  LOG_ERROR << "Sending metadata to " << secondary.getSerial() << " failed";
1226  }
1227  }
1228  }
1229 }
1230 
1231 // TODO: the function blocks until it updates all the secondaries. Consider non-blocking operation.
1232 bool SotaUptaneClient::sendMetadataToEcus(const std::vector<Uptane::Target> &targets) {
1233  Uptane::RawMetaPack meta;
1234  if (!storage->loadLatestRoot(&meta.director_root, Uptane::RepositoryType::Director())) {
1235  LOG_ERROR << "No Director Root metadata to send";
1236  return false;
1237  }
1238  if (!storage->loadNonRoot(&meta.director_targets, Uptane::RepositoryType::Director(), Uptane::Role::Targets())) {
1239  LOG_ERROR << "No Director Targets metadata to send";
1240  return false;
1241  }
1242  if (!storage->loadLatestRoot(&meta.image_root, Uptane::RepositoryType::Image())) {
1243  LOG_ERROR << "No Image repo Root metadata to send";
1244  return false;
1245  }
1246  if (!storage->loadNonRoot(&meta.image_timestamp, Uptane::RepositoryType::Image(), Uptane::Role::Timestamp())) {
1247  LOG_ERROR << "No Image repo Timestamp metadata to send";
1248  return false;
1249  }
1250  if (!storage->loadNonRoot(&meta.image_snapshot, Uptane::RepositoryType::Image(), Uptane::Role::Snapshot())) {
1251  LOG_ERROR << "No Image repo Snapshot metadata to send";
1252  return false;
1253  }
1254  if (!storage->loadNonRoot(&meta.image_targets, Uptane::RepositoryType::Image(), Uptane::Role::Targets())) {
1255  LOG_ERROR << "No Image repo Targets metadata to send";
1256  return false;
1257  }
1258 
1259  bool put_meta_succeed = true;
1260  for (const auto &target : targets) {
1261  for (const auto &ecu : target.ecus()) {
1262  const Uptane::EcuSerial ecu_serial = ecu.first;
1263  auto sec = secondaries.find(ecu_serial);
1264  if (sec == secondaries.end()) {
1265  continue;
1266  }
1267 
1268  /* Root rotation if necessary */
1269  rotateSecondaryRoot(Uptane::RepositoryType::Director(), *(sec->second));
1270  rotateSecondaryRoot(Uptane::RepositoryType::Image(), *(sec->second));
1271  if (!sec->second->putMetadata(meta)) {
1272  LOG_ERROR << "Sending metadata to " << sec->first << " failed";
1273  put_meta_succeed = false;
1274  }
1275  }
1276  }
1277 
1278  return put_meta_succeed;
1279 }
1280 
1281 std::future<data::ResultCode::Numeric> SotaUptaneClient::sendFirmwareAsync(Uptane::SecondaryInterface &secondary,
1282  const Uptane::Target &target) {
1283  auto f = [this, &secondary, target]() {
1284  const std::string &correlation_id = director_repo.getCorrelationId();
1285 
1286  sendEvent<event::InstallStarted>(secondary.getSerial());
1287  report_queue->enqueue(std_::make_unique<EcuInstallationStartedReport>(secondary.getSerial(), correlation_id));
1288 
1289  std::string data_to_send;
1290  bool send_firmware_result = false;
1291 
1292  if (target.IsOstree()) {
1293  // empty firmware means OSTree secondaries: pack credentials instead
1294  data_to_send = secondaryTreehubCredentials();
1295  } else {
1296  std::stringstream sstr;
1297  sstr << *storage->openTargetFile(target);
1298  data_to_send = sstr.str();
1299  }
1300 
1301  if (!data_to_send.empty()) {
1302  send_firmware_result = secondary.sendFirmware(data_to_send);
1303  }
1304 
1306  send_firmware_result ? data::ResultCode::Numeric::kOk : data::ResultCode::Numeric::kInstallFailed;
1307 
1308  if (send_firmware_result) {
1309  result = secondary.install(target.filename());
1310  }
1311 
1312  if (result == data::ResultCode::Numeric::kNeedCompletion) {
1313  report_queue->enqueue(std_::make_unique<EcuInstallationAppliedReport>(secondary.getSerial(), correlation_id));
1314  } else {
1315  report_queue->enqueue(std_::make_unique<EcuInstallationCompletedReport>(
1316  secondary.getSerial(), correlation_id, result == data::ResultCode::Numeric::kOk));
1317  }
1318 
1319  sendEvent<event::InstallTargetComplete>(secondary.getSerial(), result == data::ResultCode::Numeric::kOk);
1320  return result;
1321  };
1322 
1323  return std::async(std::launch::async, f);
1324 }
1325 
1326 std::vector<result::Install::EcuReport> SotaUptaneClient::sendImagesToEcus(const std::vector<Uptane::Target> &targets) {
1327  std::vector<result::Install::EcuReport> reports;
1328  std::vector<std::pair<result::Install::EcuReport, std::future<data::ResultCode::Numeric>>> firmwareFutures;
1329 
1330  const Uptane::EcuSerial &primary_ecu_serial = primaryEcuSerial();
1331  // target images should already have been downloaded to metadata_path/targets/
1332  for (auto targets_it = targets.cbegin(); targets_it != targets.cend(); ++targets_it) {
1333  for (auto ecus_it = targets_it->ecus().cbegin(); ecus_it != targets_it->ecus().cend(); ++ecus_it) {
1334  const Uptane::EcuSerial &ecu_serial = ecus_it->first;
1335 
1336  if (primary_ecu_serial == ecu_serial) {
1337  continue;
1338  }
1339 
1340  auto f = secondaries.find(ecu_serial);
1341  if (f == secondaries.end()) {
1342  LOG_ERROR << "Target " << *targets_it << " has unknown ECU ID";
1343  last_exception = Uptane::BadEcuId(targets_it->filename());
1344  continue;
1345  }
1346 
1347  Uptane::SecondaryInterface &sec = *f->second;
1348  firmwareFutures.emplace_back(result::Install::EcuReport(*targets_it, ecu_serial, data::InstallationResult()),
1349  sendFirmwareAsync(sec, *targets_it));
1350  }
1351  }
1352 
1353  for (auto &f : firmwareFutures) {
1354  data::ResultCode::Numeric fut_result = data::ResultCode::Numeric::kUnknown;
1355 
1356  fut_result = f.second.get();
1357  f.first.install_res = data::InstallationResult(fut_result, "");
1358 
1359  if (fiu_fail((std::string("secondary_install_") + f.first.serial.ToString()).c_str()) != 0) {
1360  // consider changing this approach of the fault injection, since the current approach impacts the non-test code
1361  // flow here as well as it doesn't test the installation failure on secondary from an end-to-end perspective as it
1362  // injects an error on the middle of the control flow that would have happened if an installation error had
1363  // happened in case of the virtual or the ip-secondary or any other secondary, e.g. add a mock secondary that
1364  // returns an error to sendFirmware/install request we might consider passing the installation description message
1365  // from Secondary, not just bool and/or data::ResultCode::Numeric
1366 
1367  // fault injection
1369  f.first.install_res = data::InstallationResult(data::ResultCode(fut_result, fault_injection_last_info()), "");
1370  }
1371 
1372  if (fut_result == data::ResultCode::Numeric::kOk || fut_result == data::ResultCode::Numeric::kNeedCompletion) {
1373  f.first.update.setCorrelationId(director_repo.getCorrelationId());
1374  auto update_mode = fut_result == data::ResultCode::Numeric::kOk ? InstalledVersionUpdateMode::kCurrent
1375  : InstalledVersionUpdateMode::kPending;
1376  storage->saveInstalledVersion(f.first.serial.ToString(), f.first.update, update_mode);
1377  }
1378 
1379  storage->saveEcuInstallationResult(f.first.serial, f.first.install_res);
1380  reports.push_back(f.first);
1381  }
1382  return reports;
1383 }
1384 
1385 std::string SotaUptaneClient::secondaryTreehubCredentials() const {
1386  if (config.tls.pkey_source != CryptoSource::kFile || config.tls.cert_source != CryptoSource::kFile ||
1387  config.tls.ca_source != CryptoSource::kFile) {
1388  LOG_ERROR << "Cannot send OSTree update to a secondary when not using file as credential sources";
1389  return "";
1390  }
1391  std::string ca, cert, pkey;
1392  if (!storage->loadTlsCreds(&ca, &cert, &pkey)) {
1393  LOG_ERROR << "Could not load TLS credentials from storage";
1394  return "";
1395  }
1396 
1397  std::string treehub_url = config.pacman.ostree_server;
1398  std::map<std::string, std::string> archive_map = {
1399  {"ca.pem", ca}, {"client.pem", cert}, {"pkey.pem", pkey}, {"server.url", treehub_url}};
1400 
1401  try {
1402  std::stringstream as;
1403  Utils::writeArchive(archive_map, as);
1404 
1405  return as.str();
1406  } catch (std::runtime_error &exc) {
1407  LOG_ERROR << "Could not create credentials archive: " << exc.what();
1408  return "";
1409  }
1410 }
1411 
1412 Uptane::LazyTargetsList SotaUptaneClient::allTargets() const {
1413  return Uptane::LazyTargetsList(image_repo, storage, uptane_fetcher);
1414 }
1415 
1416 void SotaUptaneClient::checkAndUpdatePendingSecondaries() {
1417  // TODO: think of another alternatives to inform Primary about installation result on Secondary ECUs (reboot case)
1418  std::vector<std::pair<Uptane::EcuSerial, Uptane::Hash>> pending_ecus;
1419  storage->getPendingEcus(&pending_ecus);
1420 
1421  for (const auto &pending_ecu : pending_ecus) {
1422  if (primaryEcuSerial() == pending_ecu.first) {
1423  continue;
1424  }
1425  auto &sec = secondaries[pending_ecu.first];
1426  const auto &manifest = sec->getManifest();
1427  if (manifest == Json::nullValue) {
1428  LOG_DEBUG << "Failed to get a manifest from Secondary: " << sec->getSerial();
1429  continue;
1430  }
1431  if (!manifest.verifySignature(sec->getPublicKey())) {
1432  LOG_ERROR << "Invalid signature of the manifest reported by secondary: "
1433  << " serial: " << pending_ecu.first << " manifest: " << manifest;
1434  // what should we do in this case ?
1435  continue;
1436  }
1437  auto current_ecu_hash = manifest.installedImageHash();
1438  if (pending_ecu.second == current_ecu_hash) {
1439  LOG_INFO << "The pending update " << current_ecu_hash << " has been installed on " << pending_ecu.first;
1440  boost::optional<Uptane::Target> pending_version;
1441  if (storage->loadInstalledVersions(pending_ecu.first.ToString(), nullptr, &pending_version)) {
1442  storage->saveEcuInstallationResult(pending_ecu.first,
1443  data::InstallationResult(data::ResultCode::Numeric::kOk, ""));
1444 
1445  storage->saveInstalledVersion(pending_ecu.first.ToString(), *pending_version,
1446  InstalledVersionUpdateMode::kCurrent);
1447 
1448  report_queue->enqueue(std_::make_unique<EcuInstallationCompletedReport>(
1449  pending_ecu.first, pending_version->correlation_id(), true));
1450 
1452  std::string raw_report;
1453  computeDeviceInstallationResult(&ir, &raw_report);
1454  storage->storeDeviceInstallationResult(ir, raw_report, pending_version->correlation_id());
1455  }
1456  }
1457  }
1458 }
1459 
1460 boost::optional<Uptane::HardwareIdentifier> SotaUptaneClient::ecuHwId(const Uptane::EcuSerial &serial) const {
1461  if (serial == primary_ecu_serial_ || serial.ToString().empty()) {
1462  if (primary_ecu_hw_id_ == Uptane::HardwareIdentifier::Unknown()) {
1463  return boost::none;
1464  }
1465  return primary_ecu_hw_id_;
1466  }
1467 
1468  const auto it = secondaries.find(serial);
1469  if (it != secondaries.end()) {
1470  return it->second->getHwId();
1471  }
1472 
1473  return boost::none;
1474 }
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:535
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:440
SecondaryInfo
Definition: invstorage.h:106
result::Download
Container for information about downloading an update.
Definition: results.h:117
api::FlowControlToken::canContinue
bool canContinue(bool blocking=true) const
Called by the controlled thread to query the currently requested state.
Definition: apiqueue.cc:33
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:193
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:184
Uptane::SecondaryInterface
Definition: secondaryinterface.h:12
Uptane::Manifest
Definition: manifest.h:13
event
Aktualizr status events.
Definition: events.h:18