| // Copyright 2020 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "shill/cellular/cellular_service_provider.h" |
| |
| #include <set> |
| #include <string> |
| #include <vector> |
| |
| #include "shill/cellular/cellular_service.h" |
| #include "shill/logging.h" |
| #include "shill/manager.h" |
| #include "shill/store_interface.h" |
| |
| namespace shill { |
| |
| namespace Logging { |
| static auto kModuleLogScope = ScopeLogger::kCellular; |
| static std::string ObjectID(const CellularServiceProvider* e) { |
| return "(cellular_service_provider)"; |
| } |
| } // namespace Logging |
| |
| namespace { |
| |
| bool GetServiceParametersFromArgs(const KeyValueStore& args, |
| std::string* imsi, |
| std::string* iccid, |
| std::string* sim_card_id, |
| Error* error) { |
| *iccid = |
| args.Lookup<std::string>(CellularService::kStorageIccid, std::string()); |
| if (iccid->empty()) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kNotSupported, |
| "Missing ICCID"); |
| return false; |
| } |
| *sim_card_id = args.Lookup<std::string>(CellularService::kStorageSimCardId, |
| std::string()); |
| if (sim_card_id->empty()) { |
| // If SIM Card Id is unset, fall back to ICCID. |
| *sim_card_id = *iccid; |
| } |
| |
| // IMSI may be empty. |
| *imsi = |
| args.Lookup<std::string>(CellularService::kStorageImsi, std::string()); |
| |
| return true; |
| } |
| |
| bool GetServiceParametersFromStorage(const StoreInterface* storage, |
| const std::string& entry_name, |
| std::string* imsi, |
| std::string* iccid, |
| std::string* sim_card_id, |
| Error* error) { |
| if (!storage->GetString(entry_name, CellularService::kStorageIccid, iccid) || |
| iccid->empty()) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kNotSupported, |
| "Missing or empty ICCID"); |
| return false; |
| } |
| if (!storage->GetString(entry_name, CellularService::kStorageSimCardId, |
| sim_card_id) || |
| sim_card_id->empty()) { |
| // If SIM Card Id is unset or empty, fall back to ICCID. |
| *sim_card_id = *iccid; |
| } |
| |
| // IMSI may be empty. |
| storage->GetString(entry_name, CellularService::kStorageImsi, imsi); |
| |
| return true; |
| } |
| |
| } // namespace |
| |
| CellularServiceProvider::CellularServiceProvider(Manager* manager) |
| : manager_(manager) {} |
| |
| CellularServiceProvider::~CellularServiceProvider() = default; |
| |
| void CellularServiceProvider::CreateServicesFromProfile( |
| const ProfileRefPtr& profile) { |
| SLOG(this, 2) << __func__ << ": " << profile->GetFriendlyName(); |
| // A Cellular Device may not exist yet, so we do not load services here. |
| // Cellular services associated with a Device are loaded in |
| // LoadServicesForDevice when the Device is created. We store |profile| here |
| // so that we always use the default profile (see comment in header). |
| if (!profile_) |
| profile_ = profile; |
| } |
| |
| ServiceRefPtr CellularServiceProvider::FindSimilarService( |
| const KeyValueStore& args, Error* error) const { |
| SLOG(this, 2) << __func__; |
| CHECK_EQ(kTypeCellular, args.Lookup<std::string>(kTypeProperty, "")) |
| << "Service type must be Cellular!"; |
| // This is called from Manager::ConfigureServiceForProfile when the Manager |
| // dbus api call is made (e.g. from Chrome) for a new service (i.e without |
| // an existing GUID). For Cellular, this should never happen. |
| Error::PopulateAndLog(FROM_HERE, error, Error::kNotSupported, |
| "Only existing Cellular services can be configured."); |
| return nullptr; |
| } |
| |
| ServiceRefPtr CellularServiceProvider::GetService(const KeyValueStore& args, |
| Error* error) { |
| SLOG(this, 2) << __func__; |
| // This is called from Manager::GetService or Manager::ConfigureService when |
| // the corresponding Manager dbus api call is made (e.g. from Chrome) for a |
| // new service (i.e without an existing GUID). For Cellular, this should never |
| // happen. |
| Error::PopulateAndLog( |
| FROM_HERE, error, Error::kNotSupported, |
| "GetService must be called with an existing Cellular Service GUID."); |
| return nullptr; |
| } |
| |
| ServiceRefPtr CellularServiceProvider::CreateTemporaryService( |
| const KeyValueStore& args, Error* error) { |
| SLOG(this, 2) << __func__; |
| std::string imsi, iccid, sim_card_id; |
| if (GetServiceParametersFromArgs(args, &imsi, &iccid, &sim_card_id, error)) |
| return new CellularService(manager_, imsi, iccid, sim_card_id); |
| return nullptr; |
| } |
| |
| ServiceRefPtr CellularServiceProvider::CreateTemporaryServiceFromProfile( |
| const ProfileRefPtr& profile, const std::string& entry_name, Error* error) { |
| SLOG(this, 2) << __func__ << ": " << profile->GetFriendlyName(); |
| std::string imsi, iccid, sim_card_id; |
| if (GetServiceParametersFromStorage(profile->GetConstStorage(), entry_name, |
| &imsi, &iccid, &sim_card_id, error)) { |
| return new CellularService(manager_, imsi, iccid, sim_card_id); |
| } |
| return nullptr; |
| } |
| |
| void CellularServiceProvider::Start() { |
| SLOG(this, 2) << __func__; |
| } |
| |
| void CellularServiceProvider::Stop() { |
| SLOG(this, 2) << __func__; |
| while (!services_.empty()) |
| RemoveService(services_.back()); |
| } |
| |
| CellularServiceRefPtr CellularServiceProvider::LoadServicesForDevice( |
| Cellular* device) { |
| CellularServiceRefPtr active_service = nullptr; |
| |
| std::string sim_card_id = device->GetSimCardId(); |
| |
| // Find Cellular profile entries matching the sim card identifier. |
| CHECK(profile_); |
| StoreInterface* storage = profile_->GetStorage(); |
| DCHECK(storage); |
| KeyValueStore args; |
| args.Set<std::string>(kTypeProperty, kTypeCellular); |
| args.Set<std::string>(CellularService::kStorageSimCardId, sim_card_id); |
| std::set<std::string> groups = storage->GetGroupsWithProperties(args); |
| |
| LOG(INFO) << __func__ << ": " << device->iccid() << ": " << groups.size(); |
| for (const std::string& group : groups) { |
| std::string imsi, iccid, service_sim_card_id; |
| if (!GetServiceParametersFromStorage(storage, group, &imsi, &iccid, |
| &service_sim_card_id, |
| /*error=*/nullptr)) { |
| LOG(ERROR) << "Unable to load service properties for: " << sim_card_id |
| << ", removing old or invalid profile entry."; |
| storage->DeleteGroup(group); |
| continue; |
| } |
| DCHECK_EQ(service_sim_card_id, sim_card_id); |
| CellularServiceRefPtr service = FindService(iccid); |
| if (!service) { |
| SLOG(this, 1) << "Loading Cellular service for ICCID: " << iccid; |
| service = new CellularService(manager_, imsi, iccid, sim_card_id); |
| service->Load(storage); |
| service->SetDevice(device); |
| AddService(service); |
| } else { |
| SLOG(this, 1) << "Cellular service exists for ICCID: " << iccid; |
| service->SetDevice(device); |
| // For Cellular, when the SIM changes or when Cellular is enabled, assume |
| // that the intent is to auto connect to the CellularService (if |
| // connectable and AutoConnect is set), even if the service was previously |
| // explicitly disconnected. |
| service->ClearExplicitlyDisconnected(); |
| } |
| if (iccid == device->iccid()) |
| active_service = service; |
| } |
| if (!active_service) { |
| SLOG(this, 1) << "No existing Cellular service with ICCID: " |
| << device->iccid(); |
| active_service = new CellularService(manager_, device->imsi(), |
| device->iccid(), sim_card_id); |
| active_service->SetDevice(device); |
| AddService(active_service); |
| } |
| |
| // Remove any remaining services not associated with |device|. |
| std::vector<CellularServiceRefPtr> services_to_remove; |
| for (CellularServiceRefPtr& service : services_) { |
| if (!service->cellular()) |
| services_to_remove.push_back(service); |
| } |
| for (CellularServiceRefPtr& service : services_to_remove) |
| RemoveService(service); |
| |
| // Set Connectable=false for visible services not matching |iccid|. |
| for (CellularServiceRefPtr& service : services_) { |
| if (service->iccid() != device->iccid()) |
| service->SetConnectable(false); |
| } |
| |
| return active_service; |
| } |
| |
| void CellularServiceProvider::RemoveServicesForDevice(Cellular* device) { |
| std::string sim_card_id = device->GetSimCardId(); |
| LOG(INFO) << __func__ << ": " << sim_card_id; |
| // Set |device| to null for services associated with |device|. When a new |
| // Cellular device is created (e.g. after a modem resets after a sim swap), |
| // services not matching the new device will be removed in |
| // LoadServicesForDevice(). This allows services to continue to exist during a |
| // modem reset when Modem and Cellular may get temporarily destroyed. |
| for (CellularServiceRefPtr& service : services_) { |
| if (service->cellular() == device) |
| service->SetDevice(nullptr); |
| } |
| } |
| |
| void CellularServiceProvider::AddService(CellularServiceRefPtr service) { |
| SLOG(this, 1) << __func__ << " with ICCID: " << service->iccid(); |
| |
| // See comment in header for |profile_|. |
| service->SetProfile(profile_); |
| // Save any changes to device properties (iccid, sim_card_id). |
| profile_->UpdateService(service); |
| manager_->RegisterService(service); |
| services_.push_back(service); |
| } |
| |
| void CellularServiceProvider::RemoveService(CellularServiceRefPtr service) { |
| SLOG(this, 1) << __func__ << " with ICCID: " << service->iccid(); |
| manager_->DeregisterService(service); |
| auto iter = std::find(services_.begin(), services_.end(), service); |
| if (iter == services_.end()) { |
| LOG(ERROR) << "RemoveService: Not found: "; |
| return; |
| } |
| services_.erase(iter); |
| } |
| |
| CellularServiceRefPtr CellularServiceProvider::FindService( |
| const std::string& iccid) { |
| const auto iter = std::find_if( |
| services_.begin(), services_.end(), |
| [iccid](const auto& service) { return service->iccid() == iccid; }); |
| if (iter != services_.end()) |
| return *iter; |
| return nullptr; |
| } |
| |
| } // namespace shill |