chromium/chrome/browser/ash/crosapi/vpn_service_ash.h

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROME_BROWSER_ASH_CROSAPI_VPN_SERVICE_ASH_H_
#define CHROME_BROWSER_ASH_CROSAPI_VPN_SERVICE_ASH_H_

#include <map>
#include <memory>
#include <string>
#include <vector>

#include "base/containers/flat_set.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/scoped_observation.h"
#include "chrome/common/extensions/api/vpn_provider.h"
#include "chromeos/ash/components/dbus/shill/shill_third_party_vpn_observer.h"
#include "chromeos/ash/components/network/network_configuration_observer.h"
#include "chromeos/ash/components/network/network_state_handler_observer.h"
#include "chromeos/crosapi/mojom/vpn_service.mojom.h"
#include "chromeos/services/network_config/public/cpp/cros_network_config_observer.h"
#include "extensions/common/extension_id.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
#include "mojo/public/cpp/bindings/remote_set.h"

namespace ash {
class NetworkConfigurationHandler;
class NetworkStateHandler;
}  // namespace ash

namespace base {
class Value;
}  // namespace base

namespace chromeos {

// Fwd for friend declaration in VpnServiceAsh.
class VpnProviderApiTestAsh;

}  // namespace chromeos

namespace crosapi {

namespace api_vpn = extensions::api::vpn_provider;

// Listens to |OnVpnProvidersChanged| event and informs the delegate of the
// current set of vpn extension.
class VpnProvidersObserver
    : public chromeos::network_config::CrosNetworkConfigObserver {
 public:
  class Delegate {
   public:
    virtual ~Delegate() = default;

    virtual void OnVpnExtensionsChanged(
        base::flat_set<std::string> vpn_extensions) = 0;
  };

  explicit VpnProvidersObserver(Delegate*);
  ~VpnProvidersObserver() override;

  // ash::network_config::CrosNetworkConfigObserver:
  void OnVpnProvidersChanged() override;

 private:
  // Callback for CrosNetworkConfig::GetVpnProviders().
  // Extracts vpn extension ids and calls Delegate::OnVpnExtensionsChanged().
  void OnGetVpnProviders(
      std::vector<chromeos::network_config::mojom::VpnProviderPtr>
          vpn_providers);

  raw_ptr<Delegate> delegate_ = nullptr;

  mojo::Remote<chromeos::network_config::mojom::CrosNetworkConfig>
      cros_network_config_;
  mojo::Receiver<chromeos::network_config::mojom::CrosNetworkConfigObserver>
      cros_network_config_observer_{this};

  base::WeakPtrFactory<VpnProvidersObserver> weak_factory_{this};
};

class VpnServiceAsh;

// This class manages configurations for a particular extension.
class VpnServiceForExtensionAsh : public crosapi::mojom::VpnServiceForExtension,
                                  public ash::NetworkConfigurationObserver {
 public:
  // Callback definitions.
  using SuccessCallback = base::OnceClosure;
  using FailureCallback =
      base::OnceCallback<void(const std::string& error_name,
                              const std::string& error_message)>;

  // Class forward declarations.
  class VpnConfiguration;

 public:
  explicit VpnServiceForExtensionAsh(const std::string& extension_id);
  ~VpnServiceForExtensionAsh() override;

  VpnServiceForExtensionAsh(const VpnServiceForExtensionAsh&) = delete;
  VpnServiceForExtensionAsh& operator=(const VpnServiceForExtensionAsh&) =
      delete;

  void BindReceiverAndObserver(
      mojo::PendingReceiver<crosapi::mojom::VpnServiceForExtension> receiver,
      mojo::PendingRemote<crosapi::mojom::EventObserverForExtension> observer);

  // crosapi::mojom::VpnServiceForExtension:
  void CreateConfiguration(const std::string& configuration_name,
                           CreateConfigurationCallback) override;
  void DestroyConfiguration(const std::string& configuration_name,
                            DestroyConfigurationCallback) override;
  void SetParameters(base::Value::Dict parameters,
                     SetParametersCallback) override;
  void SendPacket(const std::vector<uint8_t>& data,
                  SendPacketCallback) override;
  void NotifyConnectionStateChanged(
      bool connection_success,
      NotifyConnectionStateChangedCallback) override;
  void BindPepperVpnProxyObserver(
      const std::string& configuration_name,
      mojo::PendingRemote<crosapi::mojom::PepperVpnProxyObserver>
          pepper_vpn_proxy_observer,
      BindPepperVpnProxyObserverCallback) override;
  void DispatchAddDialogEvent() override;
  void DispatchConfigureDialogEvent(
      const std::string& configuration_name) override;

  // ash::NetworkConfigurationObserver:
  void OnConfigurationRemoved(const std::string& service_path,
                              const std::string& guid) override;

  bool OwnsActiveConfiguration() const;
  bool HasConfigurationForServicePath(const std::string& service_path) const;

  void DestroyAllConfigurations();

  void CreateConfigurationWithServicePath(const std::string& configuration_name,
                                          const std::string& service_path);

  void DispatchConfigRemovedEvent(const std::string& configuration_name);
  void DispatchOnPacketReceivedEvent(const std::vector<char>& data);
  void DispatchOnPlatformMessageEvent(
      const std::string& configuration_name,
      int32_t platform_message,
      const std::optional<std::string>& error = {});

 private:
  friend class VpnConfigurationImpl;
  friend class chromeos::VpnProviderApiTestAsh;
  friend class TestShillControllerAsh;

  using StringToOwnedConfigurationMap =
      std::map<std::string, std::unique_ptr<VpnConfiguration>>;
  using StringToConfigurationMap = std::map<std::string, VpnConfiguration*>;

  const extensions::ExtensionId& extension_id() const { return extension_id_; }

  // Creates a key for |key_to_configuration_map_| as a hash of |extension_id|
  // and |configuration_name|.
  static std::string GetKey(const std::string& extension_id,
                            const std::string& configuration_name);

  // Creates and adds the configuration to the internal store.
  VpnConfiguration* CreateConfigurationInternal(
      const std::string& configuration_name);

  // Removes configuration from the internal store and destroys it.
  void DestroyConfigurationInternal(VpnConfiguration*);

  // Callback used to indicate that configuration was successfully created.
  void OnCreateConfigurationSuccess(SuccessCallback,
                                    VpnConfiguration*,
                                    const std::string& service_path,
                                    const std::string& guid);

  // Callback used to indicate that configuration creation failed.
  void OnCreateConfigurationFailure(FailureCallback,
                                    VpnConfiguration*,
                                    const std::string& error_name);

  // Callback used to indicate that removing a configuration succeeded.
  void OnRemoveConfigurationSuccess(SuccessCallback);

  // Callback used to indicate that removing a configuration failed.
  void OnRemoveConfigurationFailure(FailureCallback,
                                    const std::string& error_name);

  void SetActiveConfiguration(VpnConfiguration*);

  const extensions::ExtensionId extension_id_;

  // Owns all configurations. Key is a hash of |extension_id| and
  // |configuration_name|.
  StringToOwnedConfigurationMap key_to_configuration_map_;
  // Maps shill service path to unowned configuration.
  StringToConfigurationMap service_path_to_configuration_map_;

  // Configuration that is currently in use.
  raw_ptr<VpnConfiguration> active_configuration_ = nullptr;

  base::ScopedObservation<ash::NetworkConfigurationHandler,
                          ash::NetworkConfigurationObserver>
      network_configuration_observer_{this};

  mojo::ReceiverSet<crosapi::mojom::VpnServiceForExtension> receivers_;
  mojo::RemoteSet<crosapi::mojom::EventObserverForExtension> observers_;

  base::WeakPtrFactory<VpnServiceForExtensionAsh> weak_factory_{this};
};

class VpnServiceAsh : public crosapi::mojom::VpnService,
                      public ash::NetworkStateHandlerObserver,
                      public VpnProvidersObserver::Delegate {
 public:
  VpnServiceAsh();
  ~VpnServiceAsh() override;

  VpnServiceAsh(const VpnServiceAsh&) = delete;
  VpnServiceAsh& operator=(const VpnServiceAsh&) = delete;

  // Binds |receiver| to this instance of VpnServiceAsh.
  void BindReceiver(mojo::PendingReceiver<crosapi::mojom::VpnService> receiver);

  // crosapi::mojom::VpnService:
  void RegisterVpnServiceForExtension(
      const std::string& extension_id,
      mojo::PendingReceiver<crosapi::mojom::VpnServiceForExtension> receiver,
      mojo::PendingRemote<crosapi::mojom::EventObserverForExtension> observer)
      override;
  void MaybeFailActiveConnectionAndDestroyConfigurations(
      const std::string& extension_id,
      bool destroy_configurations) override;

  // ash::NetworkStateHandlerObserver:
  void NetworkListChanged() override;

  // VpnProvidersObserver::Delegate:
  void OnVpnExtensionsChanged(
      base::flat_set<std::string> vpn_extensions) override;

 private:
  friend class chromeos::VpnProviderApiTestAsh;
  friend class VpnServiceForExtensionAsh;

  // Callback for
  // ash::NetworkConfigurationHandler::GetShillProperties(...); parses
  // the |configuration_properties| dictionary and tries to add a new
  // configuration provided that it belongs to some enabled extension.
  void OnGetShillProperties(
      const std::string& service_path,
      std::optional<base::Value::Dict> configuration_properties);

  // Always returns a valid pointer.
  VpnServiceForExtensionAsh* GetVpnServiceForExtension(
      const std::string& extension_id);

  // Ids of enabled vpn extensions.
  base::flat_set<std::string> vpn_extensions_;

  base::ScopedObservation<ash::NetworkStateHandler,
                          ash::NetworkStateHandlerObserver>
      network_state_handler_observer_{this};

  // Supports any number of receivers.
  mojo::ReceiverSet<crosapi::mojom::VpnService> receivers_;

  // Maps |extension_id| to a dedicated service for that extension.
  // We do not remove entries from this map due to various ash/lacros corner
  // cases.
  base::flat_map<std::string, std::unique_ptr<VpnServiceForExtensionAsh>>
      extension_id_to_service_;

  std::unique_ptr<VpnProvidersObserver> vpn_providers_observer_;

  base::WeakPtrFactory<VpnServiceAsh> weak_factory_{this};
};

class VpnServiceForExtensionAsh::VpnConfiguration
    : public ash::ShillThirdPartyVpnObserver {
 public:
  virtual const std::string& configuration_name() const = 0;
  virtual const std::string& key() const = 0;
  virtual const std::string& object_path() const = 0;

  virtual const std::optional<std::string>& service_path() const = 0;
  virtual void set_service_path(std::string) = 0;

  virtual void BindPepperVpnProxyObserver(
      mojo::PendingRemote<crosapi::mojom::PepperVpnProxyObserver>) = 0;
};

}  // namespace crosapi

#endif  // CHROME_BROWSER_ASH_CROSAPI_VPN_SERVICE_ASH_H_