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

#include "remoting/host/chromoting_host_services_client.h"

#include "base/check.h"
#include "base/environment.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/sequence_checker.h"
#include "base/strings/string_util.h"
#include "build/build_config.h"
#include "components/named_mojo_ipc_server/endpoint_options.h"
#include "components/named_mojo_ipc_server/named_mojo_ipc_server_client_util.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/platform/named_platform_channel.h"
#include "mojo/public/cpp/platform/platform_channel_endpoint.h"
#include "mojo/public/cpp/system/invitation.h"
#include "remoting/base/constants.h"
#include "remoting/host/ipc_constants.h"
#include "remoting/host/mojom/chromoting_host_services.mojom.h"

#if BUILDFLAG(IS_WIN)
#include <windows.h>
#endif

namespace remoting {

namespace {

mojo::PendingRemote<mojom::ChromotingHostServices> ConnectToServer(
    const std::vector<mojo::NamedPlatformChannel::ServerName>& server_names) {
  for (const auto& server_name : server_names) {
    mojo::PlatformChannelEndpoint endpoint;
#if BUILDFLAG(IS_WIN)
    mojo::NamedPlatformChannel::Options options;
    options.server_name = server_name;
    options.verify_server_privilege = true;
    endpoint = named_mojo_ipc_server::ConnectToServer(options);
#else
    endpoint = named_mojo_ipc_server::ConnectToServer(server_name);
#endif
    if (!endpoint.is_valid()) {
      VLOG(1) << "Cannot connect to IPC through server name " << server_name
              << ". Endpoint is invalid.";
      continue;
    }
#if BUILDFLAG(IS_WIN)
    DWORD peer_session_id;
    if (!GetNamedPipeServerSessionId(
            endpoint.platform_handle().GetHandle().get(), &peer_session_id)) {
      PLOG(ERROR) << "GetNamedPipeServerSessionId failed";
      continue;
    }
    // '0' (default) corresponds to the session the network process runs in.
    if (peer_session_id != 0) {
      LOG(ERROR)
          << "Cannot establish connection with IPC server running in session: "
          << peer_session_id;
      continue;
    }
#endif
    auto invitation = mojo::IncomingInvitation::Accept(std::move(endpoint));
    auto message_pipe =
        invitation.ExtractMessagePipe(kChromotingHostServicesMessagePipeId);
    return mojo::PendingRemote<mojom::ChromotingHostServices>(
        std::move(message_pipe), /* version= */ 0);
  }
  LOG(WARNING) << "Failed to connect to any ChromotingHostServices IPC server: "
               << base::JoinString(
                      server_names,
                      mojo::NamedPlatformChannel::ServerNameFromUTF8(", "));
  return {};
}

}  // namespace

ChromotingHostServicesClient::ChromotingHostServicesClient()
    : ChromotingHostServicesClient(
#if BUILDFLAG(IS_LINUX)
          std::vector{GetChromotingHostServicesServerName(),
                      GetLegacyChromotingHostServicesServerName()}
#else
          std::vector{GetChromotingHostServicesServerName()}
#endif
      ) {
}

ChromotingHostServicesClient::ChromotingHostServicesClient(
    const mojo::NamedPlatformChannel::ServerName& server_name)
    : ChromotingHostServicesClient(std::vector{server_name}) {}

ChromotingHostServicesClient::ChromotingHostServicesClient(
    const std::vector<mojo::NamedPlatformChannel::ServerName>& server_names)
    : ChromotingHostServicesClient(
          base::Environment::Create(),
          base::BindRepeating(&ConnectToServer, server_names)) {}

ChromotingHostServicesClient::ChromotingHostServicesClient(
    std::unique_ptr<base::Environment> environment,
    ConnectToServerCallback connect_to_server)
    : environment_(std::move(environment)),
      connect_to_server_(std::move(connect_to_server)) {}

ChromotingHostServicesClient::~ChromotingHostServicesClient() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

mojom::ChromotingSessionServices*
ChromotingHostServicesClient::GetSessionServices() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (!const_cast<ChromotingHostServicesClient*>(this)
           ->EnsureSessionServicesBinding()) {
    return nullptr;
  }
  return session_services_remote_.get();
}

void ChromotingHostServicesClient::set_disconnect_handler(
    base::OnceClosure disconnect_handler) {
  disconnect_handler_ = std::move(disconnect_handler);
}

bool ChromotingHostServicesClient::EnsureConnection() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (remote_.is_bound()) {
    return true;
  }

  auto pending_remote = connect_to_server_.Run();
  if (!pending_remote.is_valid()) {
    LOG(WARNING) << "Invalid message pipe.";
    return false;
  }
  remote_.Bind(std::move(pending_remote));
  remote_.set_disconnect_handler(base::BindOnce(
      &ChromotingHostServicesClient::OnDisconnected, base::Unretained(this)));
  return true;
}

bool ChromotingHostServicesClient::EnsureSessionServicesBinding() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (session_services_remote_.is_bound()) {
    return true;
  }
#if BUILDFLAG(IS_LINUX)
  if (!environment_->HasVar(kChromeRemoteDesktopSessionEnvVar)) {
    LOG(WARNING) << "Current desktop environment is not remotable.";
    return false;
  }
#endif
  if (!EnsureConnection()) {
    return false;
  }
  remote_->BindSessionServices(
      session_services_remote_.BindNewPipeAndPassReceiver());
  session_services_remote_.set_disconnect_handler(
      base::BindOnce(&ChromotingHostServicesClient::OnSessionDisconnected,
                     base::Unretained(this)));
  return true;
}

void ChromotingHostServicesClient::OnDisconnected() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  remote_.reset();

  if (disconnect_handler_) {
    std::move(disconnect_handler_).Run();
  }
}

void ChromotingHostServicesClient::OnSessionDisconnected() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  session_services_remote_.reset();

  if (disconnect_handler_) {
    std::move(disconnect_handler_).Run();
  }
}

}  // namespace remoting
