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