euicc.cc 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. // Copyright 2020 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "ash/services/cellular_setup/euicc.h"
  5. #include <cstdint>
  6. #include <memory>
  7. #include "ash/services/cellular_setup/esim_manager.h"
  8. #include "ash/services/cellular_setup/esim_mojo_utils.h"
  9. #include "ash/services/cellular_setup/esim_profile.h"
  10. #include "ash/services/cellular_setup/public/mojom/esim_manager.mojom-shared.h"
  11. #include "base/metrics/histogram_functions.h"
  12. #include "base/metrics/histogram_macros.h"
  13. #include "base/strings/strcat.h"
  14. #include "base/time/time.h"
  15. #include "chromeos/ash/components/network/cellular_connection_handler.h"
  16. #include "chromeos/ash/components/network/cellular_esim_installer.h"
  17. #include "chromeos/ash/components/network/cellular_esim_profile.h"
  18. #include "chromeos/ash/components/network/cellular_inhibitor.h"
  19. #include "chromeos/ash/components/network/hermes_metrics_util.h"
  20. #include "chromeos/ash/components/network/network_connection_handler.h"
  21. #include "chromeos/ash/components/network/network_event_log.h"
  22. #include "chromeos/ash/components/network/network_state_handler.h"
  23. #include "components/device_event_log/device_event_log.h"
  24. #include "components/qr_code_generator/qr_code_generator.h"
  25. #include "dbus/object_path.h"
  26. #include "mojo/public/cpp/bindings/pending_remote.h"
  27. #include "third_party/abseil-cpp/absl/types/optional.h"
  28. namespace ash::cellular_setup {
  29. namespace {
  30. // Delay before pending profile refresh callback is called. This ensures that
  31. // eSIM profiles are updated before callback returns.
  32. constexpr base::TimeDelta kPendingProfileRefreshDelay = base::Milliseconds(150);
  33. // Prefix for EID when encoded in QR Code.
  34. const char kEidQrCodePrefix[] = "EID:";
  35. // Measures the time from which this function is called to when |callback|
  36. // is expected to run. The measured time difference should capture the time it
  37. // took for a profile discovery request to complete.
  38. Euicc::RequestPendingProfilesCallback CreateTimedRequestPendingProfilesCallback(
  39. Euicc::RequestPendingProfilesCallback callback) {
  40. return base::BindOnce(
  41. [](Euicc::RequestPendingProfilesCallback callback,
  42. base::Time refresh_profile_start_time,
  43. mojom::ESimOperationResult result) -> void {
  44. std::move(callback).Run(result);
  45. if (result != mojom::ESimOperationResult::kSuccess)
  46. return;
  47. UMA_HISTOGRAM_MEDIUM_TIMES(
  48. "Network.Cellular.ESim.ProfileDiscovery.Latency",
  49. base::Time::Now() - refresh_profile_start_time);
  50. },
  51. std::move(callback), base::Time::Now());
  52. }
  53. } // namespace
  54. // static
  55. void Euicc::RecordRequestPendingProfilesResult(
  56. RequestPendingProfilesResult result) {
  57. base::UmaHistogramEnumeration(
  58. "Network.Cellular.ESim.RequestPendingProfiles.OperationResult", result);
  59. }
  60. Euicc::Euicc(const dbus::ObjectPath& path, ESimManager* esim_manager)
  61. : esim_manager_(esim_manager),
  62. properties_(mojom::EuiccProperties::New()),
  63. path_(path) {
  64. UpdateProperties();
  65. }
  66. Euicc::~Euicc() = default;
  67. void Euicc::GetProperties(GetPropertiesCallback callback) {
  68. std::move(callback).Run(properties_->Clone());
  69. }
  70. void Euicc::GetProfileList(GetProfileListCallback callback) {
  71. std::vector<mojo::PendingRemote<mojom::ESimProfile>> remote_list;
  72. for (auto& esim_profile : esim_profiles_) {
  73. remote_list.push_back(esim_profile->CreateRemote());
  74. }
  75. std::move(callback).Run(std::move(remote_list));
  76. }
  77. void Euicc::InstallProfileFromActivationCode(
  78. const std::string& activation_code,
  79. const std::string& confirmation_code,
  80. bool is_install_via_qr_code,
  81. InstallProfileFromActivationCodeCallback callback) {
  82. ESimProfile* profile_info = nullptr;
  83. mojom::ProfileInstallResult status =
  84. GetPendingProfileInfoFromActivationCode(activation_code, &profile_info);
  85. // Return early if profile was found but not in the correct state.
  86. if (profile_info && status != mojom::ProfileInstallResult::kSuccess) {
  87. NET_LOG(ERROR) << "EUICC could not install profile: " << status;
  88. std::move(callback).Run(status, mojo::NullRemote());
  89. return;
  90. }
  91. if (profile_info) {
  92. NET_LOG(USER) << "Installing profile with path "
  93. << profile_info->path().value();
  94. profile_info->InstallProfile(
  95. confirmation_code,
  96. base::BindOnce(
  97. [](InstallProfileFromActivationCodeCallback callback,
  98. ESimProfile* esim_profile,
  99. mojom::ProfileInstallResult status) -> void {
  100. std::move(callback).Run(status, esim_profile->CreateRemote());
  101. },
  102. std::move(callback), profile_info));
  103. return;
  104. }
  105. esim_manager_->cellular_esim_installer()->InstallProfileFromActivationCode(
  106. activation_code, confirmation_code, path_,
  107. /*new_shill_properties=*/base::DictionaryValue(),
  108. base::BindOnce(&Euicc::OnESimInstallProfileResult,
  109. weak_ptr_factory_.GetWeakPtr(), std::move(callback)),
  110. /*is_initial_install=*/true, is_install_via_qr_code);
  111. }
  112. void Euicc::OnESimInstallProfileResult(
  113. InstallProfileFromActivationCodeCallback callback,
  114. HermesResponseStatus hermes_status,
  115. absl::optional<dbus::ObjectPath> profile_path,
  116. absl::optional<std::string> /*service_path*/) {
  117. mojom::ProfileInstallResult status = InstallResultFromStatus(hermes_status);
  118. if (status != mojom::ProfileInstallResult::kSuccess) {
  119. std::move(callback).Run(status, mojo::NullRemote());
  120. return;
  121. }
  122. DCHECK(profile_path != absl::nullopt);
  123. ESimProfile* esim_profile = GetProfileFromPath(profile_path.value());
  124. if (!esim_profile) {
  125. // An ESimProfile may not exist for the newly created esim profile object
  126. // path if ESimProfileHandler has not updated profile lists yet. Save the
  127. // callback until an UpdateProfileList call creates an ESimProfile
  128. // object for this path
  129. install_calls_pending_create_.emplace(profile_path.value(),
  130. std::move(callback));
  131. return;
  132. }
  133. std::move(callback).Run(mojom::ProfileInstallResult::kSuccess,
  134. esim_profile->CreateRemote());
  135. }
  136. void Euicc::RequestPendingProfiles(RequestPendingProfilesCallback callback) {
  137. // Before requesting pending profiles, we also request installed profiles.
  138. // This ensures that if an error occurs and Chrome's installed profile cache
  139. // goes out of sync with Hermes, we re-sync at this point. See b/187459880 for
  140. // details.
  141. NET_LOG(EVENT) << "Requesting installed and pending profiles";
  142. esim_manager_->cellular_esim_profile_handler()->RefreshProfileList(
  143. path_,
  144. base::BindOnce(
  145. &Euicc::PerformRequestPendingProfiles, weak_ptr_factory_.GetWeakPtr(),
  146. CreateTimedRequestPendingProfilesCallback(std::move(callback))));
  147. }
  148. void Euicc::GetEidQRCode(GetEidQRCodeCallback callback) {
  149. // Format EID to string that should be encoded in the QRCode.
  150. std::string qr_code_string =
  151. base::StrCat({kEidQrCodePrefix, properties_->eid});
  152. QRCodeGenerator qr_generator;
  153. absl::optional<QRCodeGenerator::GeneratedCode> qr_data =
  154. qr_generator.Generate(base::as_bytes(
  155. base::make_span(qr_code_string.data(), qr_code_string.size())));
  156. if (!qr_data || qr_data->data.data() == nullptr ||
  157. qr_data->data.size() == 0) {
  158. std::move(callback).Run(nullptr);
  159. return;
  160. }
  161. // Data returned from QRCodeGenerator consist of bytes that represents
  162. // tiles. Least significant bit of each byte is set if the tile should be
  163. // filled. Other bit positions indicate QR Code structure and are not required
  164. // for rendering. Convert this data to 0 or 1 values for simpler UI side
  165. // rendering.
  166. for (uint8_t& qr_data_byte : qr_data->data) {
  167. qr_data_byte &= 1;
  168. }
  169. mojom::QRCodePtr qr_code = mojom::QRCode::New();
  170. qr_code->size = qr_data->qr_size;
  171. qr_code->data.assign(qr_data->data.begin(), qr_data->data.end());
  172. std::move(callback).Run(std::move(qr_code));
  173. }
  174. void Euicc::UpdateProfileList(
  175. const std::vector<CellularESimProfile>& esim_profile_states) {
  176. std::vector<ESimProfile*> newly_created_profiles;
  177. bool profile_list_changed = false;
  178. for (auto esim_profile_state : esim_profile_states) {
  179. if (esim_profile_state.eid() != properties_->eid) {
  180. continue;
  181. }
  182. ESimProfile* new_profile = UpdateOrCreateESimProfile(esim_profile_state);
  183. if (new_profile) {
  184. profile_list_changed = true;
  185. newly_created_profiles.push_back(new_profile);
  186. }
  187. }
  188. profile_list_changed |= RemoveUntrackedProfiles(esim_profile_states);
  189. if (profile_list_changed) {
  190. esim_manager_->NotifyESimProfileListChanged(this);
  191. // Run any install callbacks that are pending creation of new ESimProfile
  192. // object.
  193. for (ESimProfile* esim_profile : newly_created_profiles) {
  194. auto it = install_calls_pending_create_.find(esim_profile->path());
  195. if (it == install_calls_pending_create_.end()) {
  196. continue;
  197. }
  198. std::move(it->second)
  199. .Run(mojom::ProfileInstallResult::kSuccess,
  200. esim_profile->CreateRemote());
  201. install_calls_pending_create_.erase(it);
  202. }
  203. }
  204. }
  205. void Euicc::UpdateProperties() {
  206. HermesEuiccClient::Properties* properties =
  207. HermesEuiccClient::Get()->GetProperties(path_);
  208. properties_->eid = properties->eid().value();
  209. properties_->is_active = properties->is_active().value();
  210. }
  211. mojo::PendingRemote<mojom::Euicc> Euicc::CreateRemote() {
  212. mojo::PendingRemote<mojom::Euicc> euicc_remote;
  213. receiver_set_.Add(this, euicc_remote.InitWithNewPipeAndPassReceiver());
  214. return euicc_remote;
  215. }
  216. ESimProfile* Euicc::GetProfileFromPath(const dbus::ObjectPath& path) {
  217. for (auto& esim_profile : esim_profiles_) {
  218. if (esim_profile->path() == path) {
  219. return esim_profile.get();
  220. }
  221. }
  222. return nullptr;
  223. }
  224. void Euicc::PerformRequestPendingProfiles(
  225. RequestPendingProfilesCallback callback,
  226. std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock) {
  227. if (!inhibit_lock) {
  228. NET_LOG(ERROR) << "Error requesting installed profiles. Path: "
  229. << path_.value();
  230. RecordRequestPendingProfilesResult(
  231. RequestPendingProfilesResult::kInhibitFailed);
  232. std::move(callback).Run(mojom::ESimOperationResult::kFailure);
  233. return;
  234. }
  235. NET_LOG(EVENT) << "Requesting pending profiles";
  236. HermesEuiccClient::Get()->RequestPendingProfiles(
  237. path_, /*root_smds=*/ESimManager::GetRootSmdsAddress(),
  238. base::BindOnce(&Euicc::OnRequestPendingProfilesResult,
  239. weak_ptr_factory_.GetWeakPtr(), std::move(callback),
  240. std::move(inhibit_lock)));
  241. }
  242. void Euicc::OnRequestPendingProfilesResult(
  243. RequestPendingProfilesCallback callback,
  244. std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock,
  245. HermesResponseStatus status) {
  246. hermes_metrics::LogRequestPendingProfilesResult(status);
  247. RequestPendingProfilesResult metrics_result;
  248. mojom::ESimOperationResult operation_result;
  249. if (status != HermesResponseStatus::kSuccess) {
  250. NET_LOG(ERROR) << "Request Pending events failed status=" << status;
  251. metrics_result = RequestPendingProfilesResult::kHermesRequestFailed;
  252. operation_result = mojom::ESimOperationResult::kFailure;
  253. } else {
  254. metrics_result = RequestPendingProfilesResult::kSuccess;
  255. operation_result = mojom::ESimOperationResult::kSuccess;
  256. }
  257. RecordRequestPendingProfilesResult(metrics_result);
  258. // TODO(crbug.com/1216693) Update with more robust way of waiting for eSIM
  259. // profile objects to be loaded.
  260. base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
  261. FROM_HERE, base::BindOnce(std::move(callback), operation_result),
  262. kPendingProfileRefreshDelay);
  263. // inhibit_lock goes out of scope and will uninhibit automatically.
  264. }
  265. mojom::ProfileInstallResult Euicc::GetPendingProfileInfoFromActivationCode(
  266. const std::string& activation_code,
  267. ESimProfile** profile_info) {
  268. const auto iter = base::ranges::find_if(
  269. esim_profiles_, [activation_code](const auto& esim_profile) -> bool {
  270. return esim_profile->properties()->activation_code == activation_code;
  271. });
  272. if (iter == esim_profiles_.end()) {
  273. NET_LOG(EVENT) << "Get pending profile with activation failed: No profile "
  274. "with activation_code.";
  275. return mojom::ProfileInstallResult::kFailure;
  276. }
  277. *profile_info = iter->get();
  278. if ((*profile_info)->properties()->state != mojom::ProfileState::kPending) {
  279. NET_LOG(ERROR) << "Get pending profile with activation code failed: Profile"
  280. "is not in pending state.";
  281. return mojom::ProfileInstallResult::kFailure;
  282. }
  283. return mojom::ProfileInstallResult::kSuccess;
  284. }
  285. ESimProfile* Euicc::UpdateOrCreateESimProfile(
  286. const CellularESimProfile& esim_profile_state) {
  287. ESimProfile* esim_profile = GetProfileFromPath(esim_profile_state.path());
  288. if (esim_profile) {
  289. esim_profile->UpdateProperties(esim_profile_state, /*notify=*/true);
  290. return nullptr;
  291. }
  292. esim_profiles_.push_back(
  293. std::make_unique<ESimProfile>(esim_profile_state, this, esim_manager_));
  294. return esim_profiles_.back().get();
  295. }
  296. bool Euicc::RemoveUntrackedProfiles(
  297. const std::vector<CellularESimProfile>& esim_profile_states) {
  298. std::set<std::string> new_iccids;
  299. for (auto esim_profile_state : esim_profile_states) {
  300. if (esim_profile_state.eid() != properties_->eid) {
  301. continue;
  302. }
  303. new_iccids.insert(esim_profile_state.iccid());
  304. }
  305. bool removed = false;
  306. for (auto it = esim_profiles_.begin(); it != esim_profiles_.end();) {
  307. ESimProfile* profile = (*it).get();
  308. if (new_iccids.find(profile->properties()->iccid) == new_iccids.end()) {
  309. profile->OnProfileRemove();
  310. it = esim_profiles_.erase(it);
  311. removed = true;
  312. } else {
  313. it++;
  314. }
  315. }
  316. return removed;
  317. }
  318. } // namespace ash::cellular_setup