chromium/chromeos/ash/components/network/network_sms_handler.cc

// 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.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#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/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());
  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;

  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()));

  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);
}

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