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