| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chromeos/ash/components/network/network_sms_handler.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <algorithm> |
| #include <memory> |
| #include <vector> |
| |
| #include "base/check.h" |
| #include "base/compiler_specific.h" |
| #include "base/containers/circular_deque.h" |
| #include "base/functional/bind.h" |
| #include "base/logging.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/time/time.h" |
| #include "base/timer/timer.h" |
| #include "base/values.h" |
| #include "chromeos/ash/components/dbus/shill/modem_messaging_client.h" |
| #include "chromeos/ash/components/dbus/shill/shill_device_client.h" |
| #include "chromeos/ash/components/dbus/shill/shill_manager_client.h" |
| #include "chromeos/ash/components/dbus/shill/sms_client.h" |
| #include "chromeos/ash/components/network/network_state_handler.h" |
| #include "chromeos/ash/components/network/network_type_pattern.h" |
| #include "components/device_event_log/device_event_log.h" |
| #include "dbus/object_path.h" |
| #include "third_party/cros_system_api/dbus/service_constants.h" |
| #include "third_party/cros_system_api/dbus/shill/dbus-constants.h" |
| |
| namespace { |
| |
| // Key for the device GUID in message data received from the |
| // NetworkSmsDeviceHandler. |
| const char kNetworkGuidKey[] = "GUID"; |
| |
| // Maximum number of messages stored for RequestUpdate(true). |
| const size_t kMaxReceivedMessages = 100; |
| |
| std::optional<const std::string> GetStringOptional( |
| const base::Value::Dict& dict, |
| const std::string& key) { |
| if (!dict.FindString(key)) { |
| return std::nullopt; |
| } |
| return *dict.FindString(key); |
| } |
| |
| } // namespace |
| |
| namespace ash { |
| |
| // static |
| const char NetworkSmsHandler::kNumberKey[] = "number"; |
| const char NetworkSmsHandler::kTextKey[] = "text"; |
| const char NetworkSmsHandler::kTimestampKey[] = "timestamp"; |
| const base::TimeDelta NetworkSmsHandler::kFetchSmsDetailsTimeout = |
| base::Seconds(60); |
| |
| TextMessageData::TextMessageData(std::optional<const std::string> number, |
| std::optional<const std::string> text, |
| std::optional<const std::string> timestamp) |
| : number(number), text(text), timestamp(timestamp) {} |
| |
| TextMessageData::~TextMessageData() = default; |
| |
| TextMessageData::TextMessageData(TextMessageData&& other) { |
| number = std::move(other.number); |
| text = std::move(other.text); |
| timestamp = std::move(other.timestamp); |
| } |
| |
| TextMessageData& TextMessageData::operator=(TextMessageData&& other) { |
| number = std::move(other.number); |
| text = std::move(other.text); |
| timestamp = std::move(other.timestamp); |
| return *this; |
| } |
| |
| class NetworkSmsHandler::NetworkSmsDeviceHandler { |
| public: |
| NetworkSmsDeviceHandler() = default; |
| virtual ~NetworkSmsDeviceHandler() = default; |
| |
| // Updates the last active network's GUID for the current device handler. |
| virtual void SetLastActiveNetwork(const NetworkState* state) {} |
| }; |
| |
| class NetworkSmsHandler::ModemManager1NetworkSmsDeviceHandler |
| : public NetworkSmsHandler::NetworkSmsDeviceHandler { |
| public: |
| ModemManager1NetworkSmsDeviceHandler(NetworkSmsHandler* host, |
| const std::string& service_name, |
| const dbus::ObjectPath& object_path); |
| |
| ModemManager1NetworkSmsDeviceHandler( |
| const ModemManager1NetworkSmsDeviceHandler&) = delete; |
| ModemManager1NetworkSmsDeviceHandler& operator=( |
| const ModemManager1NetworkSmsDeviceHandler&) = delete; |
| |
| ~ModemManager1NetworkSmsDeviceHandler() override; |
| |
| private: |
| void ListCallback(std::optional<std::vector<dbus::ObjectPath>> paths); |
| void SmsReceivedCallback(const dbus::ObjectPath& path, bool complete); |
| void GetCallback(const dbus::ObjectPath& sms_path, |
| const base::Value::Dict& dictionary); |
| void DeleteMessages(); |
| void DeleteCallback(const dbus::ObjectPath& sms_path, bool success); |
| void GetMessages(); |
| void MessageReceived(const base::Value::Dict& dictionary); |
| void OnFetchSmsDetailsTimeout(const dbus::ObjectPath& sms_path); |
| void SetLastActiveNetwork(const NetworkState* state) override; |
| |
| raw_ptr<NetworkSmsHandler> host_; |
| std::string service_name_; |
| dbus::ObjectPath object_path_; |
| base::OneShotTimer fetch_sms_details_timer_; |
| bool deleting_messages_ = false; |
| bool retrieving_messages_ = false; |
| std::vector<dbus::ObjectPath> delete_queue_; |
| base::circular_deque<dbus::ObjectPath> retrieval_queue_; |
| std::string last_active_network_guid_; |
| base::WeakPtrFactory<ModemManager1NetworkSmsDeviceHandler> weak_ptr_factory_{ |
| this}; |
| }; |
| |
| NetworkSmsHandler::ModemManager1NetworkSmsDeviceHandler:: |
| ModemManager1NetworkSmsDeviceHandler(NetworkSmsHandler* host, |
| const std::string& service_name, |
| const dbus::ObjectPath& object_path) |
| : host_(host), service_name_(service_name), object_path_(object_path) { |
| NET_LOG(DEBUG) |
| << "SMS handler for " << object_path.value() |
| << " created, setting SMS receive handler and fetching existing messages"; |
| |
| // Set the handler for received Sms messages. |
| ModemMessagingClient::Get()->SetSmsReceivedHandler( |
| service_name_, object_path_, |
| base::BindRepeating( |
| &NetworkSmsHandler::ModemManager1NetworkSmsDeviceHandler:: |
| SmsReceivedCallback, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| // List the existing messages. |
| ModemMessagingClient::Get()->List( |
| service_name_, object_path_, |
| base::BindOnce(&NetworkSmsHandler::ModemManager1NetworkSmsDeviceHandler:: |
| ListCallback, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| NetworkSmsHandler::ModemManager1NetworkSmsDeviceHandler:: |
| ~ModemManager1NetworkSmsDeviceHandler() { |
| ModemMessagingClient::Get()->ResetSmsReceivedHandler(service_name_, |
| object_path_); |
| } |
| |
| void NetworkSmsHandler::ModemManager1NetworkSmsDeviceHandler::ListCallback( |
| std::optional<std::vector<dbus::ObjectPath>> paths) { |
| // This receives all messages, so clear any pending gets and deletes. |
| retrieval_queue_.clear(); |
| delete_queue_.clear(); |
| |
| if (!paths.has_value()) { |
| NET_LOG(DEBUG) << "No paths returned"; |
| return; |
| } |
| |
| NET_LOG(EVENT) << "Bulk fetched [" << paths->size() << "] message(s)"; |
| retrieval_queue_.reserve(paths->size()); |
| UNSAFE_TODO(retrieval_queue_.assign(std::make_move_iterator(paths->begin()), |
| std::make_move_iterator(paths->end()))); |
| if (retrieving_messages_) { |
| NET_LOG(DEBUG) << "Already retrieving messages, not starting queue"; |
| return; |
| } |
| |
| GetMessages(); |
| } |
| |
| // Messages must be deleted one at a time, since we can not guarantee |
| // the order the deletion will be executed in. Delete messages from |
| // the back of the list so that the indices are valid. |
| void NetworkSmsHandler::ModemManager1NetworkSmsDeviceHandler::DeleteMessages() { |
| if (delete_queue_.empty()) { |
| NET_LOG(DEBUG) << "Delete queue is empty, finished deleting messages"; |
| deleting_messages_ = false; |
| return; |
| } |
| deleting_messages_ = true; |
| dbus::ObjectPath sms_path = std::move(delete_queue_.back()); |
| delete_queue_.pop_back(); |
| NET_LOG(EVENT) << "Deleting " << sms_path.value() << ", [" |
| << delete_queue_.size() |
| << "] message(s) left in the deletion queue"; |
| ModemMessagingClient::Get()->Delete( |
| service_name_, object_path_, sms_path, |
| base::BindOnce(&NetworkSmsHandler::ModemManager1NetworkSmsDeviceHandler:: |
| DeleteCallback, |
| weak_ptr_factory_.GetWeakPtr(), sms_path)); |
| } |
| |
| void NetworkSmsHandler::ModemManager1NetworkSmsDeviceHandler::DeleteCallback( |
| const dbus::ObjectPath& sms_path, |
| bool success) { |
| if (!success) { |
| // Add the SMS to the queue so that it can eventually get retried. |
| delete_queue_.insert(delete_queue_.begin(), sms_path); |
| NET_LOG(ERROR) << "Delete failed for " << sms_path.value() |
| << ", inserted message back into the deletion queue, [" |
| << delete_queue_.size() |
| << "] message(s) left in the deletion queue"; |
| |
| // Set the flag back to false so that new deletion attempts can occur the |
| // next time a message is received. |
| deleting_messages_ = false; |
| return; |
| } |
| |
| NET_LOG(EVENT) << "Delete succeeded for " << sms_path.value(); |
| DeleteMessages(); |
| } |
| |
| // Messages must be fetched one at a time, so that we do not queue too |
| // many requests to a single threaded server. |
| void NetworkSmsHandler::ModemManager1NetworkSmsDeviceHandler::GetMessages() { |
| if (retrieval_queue_.empty()) { |
| NET_LOG(DEBUG) << "Retrieval queue is empty, finished retrieving messages"; |
| retrieving_messages_ = false; |
| if (!deleting_messages_) |
| DeleteMessages(); |
| return; |
| } |
| retrieving_messages_ = true; |
| dbus::ObjectPath sms_path = retrieval_queue_.front(); |
| retrieval_queue_.pop_front(); |
| NET_LOG(EVENT) << "Fetching details for " << sms_path.value() << ", [" |
| << retrieval_queue_.size() |
| << "] message(s) left in the retrieval queue"; |
| fetch_sms_details_timer_.Start( |
| FROM_HERE, kFetchSmsDetailsTimeout, |
| base::BindOnce(&NetworkSmsHandler::ModemManager1NetworkSmsDeviceHandler:: |
| OnFetchSmsDetailsTimeout, |
| weak_ptr_factory_.GetWeakPtr(), sms_path)); |
| SMSClient::Get()->GetAll( |
| service_name_, sms_path, |
| base::BindOnce( |
| &NetworkSmsHandler::ModemManager1NetworkSmsDeviceHandler::GetCallback, |
| weak_ptr_factory_.GetWeakPtr(), sms_path)); |
| delete_queue_.push_back(sms_path); |
| } |
| |
| void NetworkSmsHandler::ModemManager1NetworkSmsDeviceHandler:: |
| SmsReceivedCallback(const dbus::ObjectPath& sms_path, bool complete) { |
| NET_LOG(EVENT) << "Message received: " << sms_path.value(); |
| // Only handle complete messages. |
| if (!complete) { |
| NET_LOG(DEBUG) << "Message is not complete, not handling: " |
| << sms_path.value(); |
| return; |
| } |
| |
| retrieval_queue_.push_back(sms_path); |
| if (retrieving_messages_) { |
| NET_LOG(DEBUG) |
| << "SMS received but already retrieving messages, not starting queue"; |
| return; |
| } |
| |
| GetMessages(); |
| } |
| |
| void NetworkSmsHandler::ModemManager1NetworkSmsDeviceHandler::GetCallback( |
| const dbus::ObjectPath& sms_path, |
| const base::Value::Dict& dictionary) { |
| NET_LOG(EVENT) << "Message details fetched for: " << sms_path.value(); |
| fetch_sms_details_timer_.Stop(); |
| MessageReceived(dictionary); |
| GetMessages(); |
| } |
| |
| void NetworkSmsHandler::ModemManager1NetworkSmsDeviceHandler::MessageReceived( |
| const base::Value::Dict& dictionary) { |
| // The keys of the ModemManager1.SMS interface do not match the exported keys, |
| // so a new dictionary is created with the expected key names. |
| base::Value::Dict new_dictionary; |
| const std::string* number = |
| dictionary.FindString(SMSClient::kSMSPropertyNumber); |
| if (number) { |
| new_dictionary.Set(kNumberKey, *number); |
| } |
| const std::string* text = dictionary.FindString(SMSClient::kSMSPropertyText); |
| if (text) { |
| new_dictionary.Set(kTextKey, *text); |
| } |
| const std::string* timestamp = |
| dictionary.FindString(SMSClient::kSMSPropertyTimestamp); |
| if (timestamp) { |
| new_dictionary.Set(kTimestampKey, *timestamp); |
| } |
| |
| new_dictionary.Set(kNetworkGuidKey, last_active_network_guid_); |
| |
| host_->MessageReceived(new_dictionary); |
| } |
| |
| void NetworkSmsHandler::ModemManager1NetworkSmsDeviceHandler:: |
| OnFetchSmsDetailsTimeout(const dbus::ObjectPath& sms_path) { |
| NET_LOG(ERROR) << "SMSClient::GetAll() timed out for " << sms_path.value() |
| << ", moving to next message."; |
| GetMessages(); |
| } |
| |
| void NetworkSmsHandler::ModemManager1NetworkSmsDeviceHandler:: |
| SetLastActiveNetwork(const NetworkState* network_state) { |
| if (!network_state) { |
| return; |
| } |
| NET_LOG(DEBUG) << "Updating last seen network to network with GUID: " |
| << network_state->guid(); |
| last_active_network_guid_ = network_state->guid(); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // NetworkSmsHandler |
| |
| NetworkSmsHandler::NetworkSmsHandler() = default; |
| |
| NetworkSmsHandler::~NetworkSmsHandler() { |
| ShillManagerClient::Get()->RemovePropertyChangedObserver(this); |
| if (!cellular_device_path_.empty()) { |
| ShillDeviceClient::Get()->RemovePropertyChangedObserver( |
| dbus::ObjectPath(cellular_device_path_), this); |
| } |
| } |
| |
| void NetworkSmsHandler::Init() { |
| // Add as an observer here so that new devices added after this call are |
| // recognized. |
| ShillManagerClient::Get()->AddPropertyChangedObserver(this); |
| // Request network manager properties so that we can get the list of devices. |
| ShillManagerClient::Get()->GetProperties( |
| base::BindOnce(&NetworkSmsHandler::ManagerPropertiesCallback, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void NetworkSmsHandler::Init(NetworkStateHandler* network_state_handler) { |
| network_state_handler_ = network_state_handler; |
| network_state_handler_observation_.Observe(network_state_handler_); |
| Init(); |
| } |
| |
| void NetworkSmsHandler::RequestUpdate() { |
| for (const auto& message : received_messages_) { |
| NotifyMessageReceived(message); |
| } |
| } |
| |
| void NetworkSmsHandler::AddObserver(Observer* observer) { |
| observers_.AddObserver(observer); |
| } |
| |
| void NetworkSmsHandler::RemoveObserver(Observer* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| void NetworkSmsHandler::OnPropertyChanged(const std::string& name, |
| const base::Value& value) { |
| // Device property change |
| if (name == shill::kDBusObjectProperty) { |
| OnObjectPathChanged(value); |
| return; |
| } |
| |
| // Manager property change |
| if (name == shill::kDevicesProperty && value.is_list()) { |
| UpdateDevices(value.GetList()); |
| } |
| |
| if (name == shill::kIccidProperty && value.is_string()) { |
| OnActiveDeviceIccidChanged(value.GetString()); |
| } |
| } |
| |
| void NetworkSmsHandler::OnActiveDeviceIccidChanged(const std::string& iccid) { |
| if (!device_handler_ || iccid.empty()) { |
| return; |
| } |
| NetworkStateHandler::NetworkStateList active_networks; |
| // We also look at non-active networks, to account for networks that are |
| // disconnected as you can receive text messages on active devices with |
| // disconnected networks. |
| network_state_handler_->GetNetworkListByType( |
| NetworkTypePattern::Cellular(), /*configured_only=*/false, |
| /*visible_only=*/false, /*limit=*/0, &active_networks); |
| for (auto* network : active_networks) { |
| if (network->iccid() == iccid) { |
| device_handler_->SetLastActiveNetwork(network); |
| return; |
| } |
| } |
| } |
| |
| void NetworkSmsHandler::ActiveNetworksChanged( |
| const std::vector<const NetworkState*>& active_networks) { |
| for (const NetworkState* network : active_networks) { |
| if (network->type() == shill::kTypeCellular && device_handler_) { |
| device_handler_->SetLastActiveNetwork(network); |
| break; |
| } |
| } |
| } |
| |
| // Private methods |
| |
| void NetworkSmsHandler::AddReceivedMessage(const base::Value::Dict& message) { |
| if (received_messages_.size() >= kMaxReceivedMessages) |
| received_messages_.erase(received_messages_.begin()); |
| received_messages_.push_back(message.Clone()); |
| } |
| |
| void NetworkSmsHandler::NotifyMessageReceived( |
| const base::Value::Dict& message) { |
| TextMessageData message_data{GetStringOptional(message, kNumberKey), |
| GetStringOptional(message, kTextKey), |
| GetStringOptional(message, kTimestampKey)}; |
| |
| const std::string network_guid = |
| GetStringOptional(message, kNetworkGuidKey).value_or(std::string()); |
| if (network_guid.empty()) { |
| NET_LOG(ERROR) << "Message received with an empty GUID"; |
| } |
| |
| for (auto& observer : observers_) { |
| observer.MessageReceivedFromNetwork(network_guid, message_data); |
| } |
| } |
| |
| void NetworkSmsHandler::MessageReceived(const base::Value::Dict& message) { |
| AddReceivedMessage(message); |
| NotifyMessageReceived(message); |
| } |
| |
| void NetworkSmsHandler::ManagerPropertiesCallback( |
| std::optional<base::Value::Dict> properties) { |
| if (!properties) { |
| NET_LOG(ERROR) << "NetworkSmsHandler: Failed to get manager properties."; |
| return; |
| } |
| const base::Value::List* value = |
| properties->FindList(shill::kDevicesProperty); |
| if (!value) { |
| NET_LOG(EVENT) << "NetworkSmsHandler: No list value for: " |
| << shill::kDevicesProperty; |
| return; |
| } |
| UpdateDevices(*value); |
| } |
| |
| void NetworkSmsHandler::UpdateDevices(const base::Value::List& devices) { |
| for (const auto& item : devices) { |
| if (!item.is_string()) |
| continue; |
| |
| std::string device_path = item.GetString(); |
| if (!device_path.empty()) { |
| // Request device properties. |
| NET_LOG(DEBUG) << "GetDeviceProperties: " << device_path; |
| ShillDeviceClient::Get()->GetProperties( |
| dbus::ObjectPath(device_path), |
| base::BindOnce(&NetworkSmsHandler::DevicePropertiesCallback, |
| weak_ptr_factory_.GetWeakPtr(), device_path)); |
| } |
| } |
| } |
| |
| void NetworkSmsHandler::DevicePropertiesCallback( |
| const std::string& device_path, |
| std::optional<base::Value::Dict> properties) { |
| if (!properties) { |
| NET_LOG(ERROR) << "NetworkSmsHandler error for: " << device_path; |
| return; |
| } |
| |
| const std::string* device_type = properties->FindString(shill::kTypeProperty); |
| if (!device_type) { |
| NET_LOG(ERROR) << "NetworkSmsHandler: No type for: " << device_path; |
| return; |
| } |
| if (*device_type != shill::kTypeCellular) |
| return; |
| |
| const std::string* service_name = |
| properties->FindString(shill::kDBusServiceProperty); |
| if (!service_name) { |
| NET_LOG(ERROR) << "Device has no DBusService Property: " << device_path; |
| return; |
| } |
| |
| if (*service_name != modemmanager::kModemManager1ServiceName) |
| return; |
| |
| // Add observer for object path property. |
| if (cellular_device_path_ != device_path) { |
| if (!cellular_device_path_.empty()) { |
| ShillDeviceClient::Get()->RemovePropertyChangedObserver( |
| dbus::ObjectPath(cellular_device_path_), this); |
| } |
| cellular_device_path_ = device_path; |
| ShillDeviceClient::Get()->AddPropertyChangedObserver( |
| dbus::ObjectPath(cellular_device_path_), this); |
| } |
| |
| const std::string* object_path_string = |
| properties->FindString(shill::kDBusObjectProperty); |
| if (!object_path_string || object_path_string->empty()) { |
| NET_LOG(ERROR) << "Device has no or empty DBusObject Property: " |
| << device_path; |
| return; |
| } |
| dbus::ObjectPath object_path(*object_path_string); |
| |
| // Destroy |device_handler_| first to reset the current SmsReceivedHandler. |
| // Only one active handler is supported. TODO(crbug.com/1239418): Fix. |
| device_handler_.reset(); |
| |
| device_handler_ = std::make_unique<ModemManager1NetworkSmsDeviceHandler>( |
| this, *service_name, object_path); |
| |
| OnActiveDeviceIccidChanged( |
| GetStringOptional(*properties, shill::kIccidProperty) |
| .value_or(std::string())); |
| device_handler_->SetLastActiveNetwork( |
| network_state_handler_->ConnectedNetworkByType( |
| NetworkTypePattern::Cellular())); |
| } |
| |
| void NetworkSmsHandler::OnObjectPathChanged(const base::Value& object_path) { |
| // Remove the old handler. |
| device_handler_.reset(); |
| |
| std::string object_path_string = |
| object_path.is_string() ? object_path.GetString() : ""; |
| // If the new object path is empty, there is no SIM. Don't create a new |
| // handler. |
| if (object_path_string.empty() || object_path_string == "/") { |
| return; |
| } |
| |
| // Recreate handler for the new object path. |
| ShillManagerClient::Get()->GetProperties( |
| base::BindOnce(&NetworkSmsHandler::ManagerPropertiesCallback, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| } // namespace ash |