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