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