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