text: // Copyright (c) 2012 The Chromium 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 "remoting/host/chromoting_host.h"
  
  #include <algorithm>
  
  #include "base/bind.h"
  #include "base/callback.h"
  #include "base/logging.h"
  #include "base/message_loop/message_loop_proxy.h"
  #include "build/build_config.h"
  #include "remoting/base/constants.h"
  #include "remoting/host/chromoting_host_context.h"
  #include "remoting/host/desktop_environment.h"
  #include "remoting/host/host_config.h"
  #include "remoting/host/input_injector.h"
  #include "remoting/protocol/connection_to_client.h"
  #include "remoting/protocol/client_stub.h"
  #include "remoting/protocol/host_stub.h"
  #include "remoting/protocol/input_stub.h"
  #include "remoting/protocol/session_config.h"
  
  using remoting::protocol::ConnectionToClient;
  using remoting::protocol::InputStub;
  
  namespace remoting {
  
  namespace {
  
  const net::BackoffEntry::Policy kDefaultBackoffPolicy = {
    // Number of initial errors (in sequence) to ignore before applying
    // exponential back-off rules.
    0,
  
    // Initial delay for exponential back-off in ms.
    2000,
  
    // Factor by which the waiting time will be multiplied.
    2,
  
    // Fuzzing percentage. ex: 10% will spread requests randomly
    // between 90%-100% of the calculated time.
    0,
  
    // Maximum amount of time we are willing to delay our request in ms.
    -1,
  
    // Time to keep an entry from being discarded even when it
    // has no significant state, -1 to never discard.
    -1,
  
    // Don't use initial delay unless the last request was an error.
    false,
  };
  
  }  // namespace
  
  ChromotingHost::ChromotingHost(
      SignalStrategy* signal_strategy,
      DesktopEnvironmentFactory* desktop_environment_factory,
      scoped_ptr<protocol::SessionManager> session_manager,
      scoped_refptr<base::SingleThreadTaskRunner> audio_task_runner,
      scoped_refptr<base::SingleThreadTaskRunner> input_task_runner,
      scoped_refptr<base::SingleThreadTaskRunner> video_capture_task_runner,
      scoped_refptr<base::SingleThreadTaskRunner> video_encode_task_runner,
      scoped_refptr<base::SingleThreadTaskRunner> network_task_runner,
      scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner)
      : desktop_environment_factory_(desktop_environment_factory),
        session_manager_(session_manager.Pass()),
        audio_task_runner_(audio_task_runner),
        input_task_runner_(input_task_runner),
        video_capture_task_runner_(video_capture_task_runner),
        video_encode_task_runner_(video_encode_task_runner),
        network_task_runner_(network_task_runner),
        ui_task_runner_(ui_task_runner),
        signal_strategy_(signal_strategy),
        started_(false),
        protocol_config_(protocol::CandidateSessionConfig::CreateDefault()),
        login_backoff_(&kDefaultBackoffPolicy),
        authenticating_client_(false),
        reject_authenticating_client_(false),
        enable_curtaining_(false),
        weak_factory_(this) {
    DCHECK(network_task_runner_->BelongsToCurrentThread());
    DCHECK(signal_strategy);
  
    if (!desktop_environment_factory_->SupportsAudioCapture()) {
      protocol::CandidateSessionConfig::DisableAudioChannel(
          protocol_config_.get());
    }
  }
  
  ChromotingHost::~ChromotingHost() {
    DCHECK(CalledOnValidThread());
  
    // Disconnect all of the clients.
    while (!clients_.empty()) {
      clients_.front()->DisconnectSession();
    }
  
    // Destroy the session manager to make sure that |signal_strategy_| does not
    // have any listeners registered.
    session_manager_.reset();
  
    // Notify observers.
    if (started_)
      FOR_EACH_OBSERVER(HostStatusObserver, status_observers_, OnShutdown());
  }
  
  void ChromotingHost::Start(const std::string& xmpp_login) {
    DCHECK(CalledOnValidThread());
    DCHECK(!started_);
  
    LOG(INFO) << "Starting host";
    started_ = true;
    FOR_EACH_OBSERVER(HostStatusObserver, status_observers_, OnStart(xmpp_login));
  
    // Start the SessionManager, supplying this ChromotingHost as the listener.
    session_manager_->Init(signal_strategy_, this);
  }
  
  void ChromotingHost::AddStatusObserver(HostStatusObserver* observer) {
    DCHECK(CalledOnValidThread());
    status_observers_.AddObserver(observer);
  }
  
  void ChromotingHost::RemoveStatusObserver(HostStatusObserver* observer) {
    DCHECK(CalledOnValidThread());
    status_observers_.RemoveObserver(observer);
  }
  
  void ChromotingHost::RejectAuthenticatingClient() {
    DCHECK(authenticating_client_);
    reject_authenticating_client_ = true;
  }
  
  void ChromotingHost::SetAuthenticatorFactory(
      scoped_ptr<protocol::AuthenticatorFactory> authenticator_factory) {
    DCHECK(CalledOnValidThread());
    session_manager_->set_authenticator_factory(authenticator_factory.Pass());
  }
  
  void ChromotingHost::SetEnableCurtaining(bool enable) {
    DCHECK(network_task_runner_->BelongsToCurrentThread());
  
    if (enable_curtaining_ == enable)
      return;
  
    enable_curtaining_ = enable;
    desktop_environment_factory_->SetEnableCurtaining(enable_curtaining_);
  
    // Disconnect all existing clients because they might be running not
    // curtained.
    // TODO(alexeypa): fix this such that the curtain is applied to the not
    // curtained sessions or disconnect only the client connected to not
    // curtained sessions.
    if (enable_curtaining_)
      DisconnectAllClients();
  }
  
  void ChromotingHost::SetMaximumSessionDuration(
      const base::TimeDelta& max_session_duration) {
    max_session_duration_ = max_session_duration;
  }
  
  ////////////////////////////////////////////////////////////////////////////
  // protocol::ClientSession::EventHandler implementation.
  bool ChromotingHost::OnSessionAuthenticated(ClientSession* client) {
    DCHECK(CalledOnValidThread());
  
    login_backoff_.Reset();
  
    // Disconnect all other clients. |it| should be advanced before Disconnect()
    // is called to avoid it becoming invalid when the client is removed from
    // the list.
    ClientList::iterator it = clients_.begin();
    while (it != clients_.end()) {
      ClientSession* other_client = *it++;
      if (other_client != client)
        other_client->DisconnectSession();
    }
  
    // Disconnects above must have destroyed all other clients.
    DCHECK_EQ(clients_.size(), 1U);
  
    // Notify observers that there is at least one authenticated client.
    const std::string& jid = client->client_jid();
  
    reject_authenticating_client_ = false;
  
    authenticating_client_ = true;
    FOR_EACH_OBSERVER(HostStatusObserver, status_observers_,
                      OnClientAuthenticated(jid));
    authenticating_client_ = false;
  
    return !reject_authenticating_client_;
  }
  
  void ChromotingHost::OnSessionChannelsConnected(ClientSession* client) {
    DCHECK(CalledOnValidThread());
  
    // Notify observers.
    FOR_EACH_OBSERVER(HostStatusObserver, status_observers_,
                      OnClientConnected(client->client_jid()));
  }
  
  void ChromotingHost::OnSessionAuthenticationFailed(ClientSession* client) {
    DCHECK(CalledOnValidThread());
  
    // Notify observers.
    FOR_EACH_OBSERVER(HostStatusObserver, status_observers_,
                      OnAccessDenied(client->client_jid()));
  }
  
  void ChromotingHost::OnSessionClosed(ClientSession* client) {
    DCHECK(CalledOnValidThread());
  
    ClientList::iterator it = std::find(clients_.begin(), clients_.end(), client);
    CHECK(it != clients_.end());
  
    if (client->is_authenticated()) {
      FOR_EACH_OBSERVER(HostStatusObserver, status_observers_,
                        OnClientDisconnected(client->client_jid()));
    }
  
    clients_.erase(it);
    delete client;
  }
  
  void ChromotingHost::OnSessionSequenceNumber(ClientSession* session,
                                               int64 sequence_number) {
    DCHECK(CalledOnValidThread());
  }
  
  void ChromotingHost::OnSessionRouteChange(
      ClientSession* session,
      const std::string& channel_name,
      const protocol::TransportRoute& route) {
    DCHECK(CalledOnValidThread());
    FOR_EACH_OBSERVER(HostStatusObserver, status_observers_,
                      OnClientRouteChange(session->client_jid(), channel_name,
                                          route));
  }
  
  void ChromotingHost::OnSessionManagerReady() {
    DCHECK(CalledOnValidThread());
    // Don't need to do anything here, just wait for incoming
    // connections.
  }
  
  void ChromotingHost::OnIncomingSession(
        protocol::Session* session,
        protocol::SessionManager::IncomingSessionResponse* response) {
    DCHECK(CalledOnValidThread());
  
    if (!started_) {
      *response = protocol::SessionManager::DECLINE;
      return;
    }
  
    if (login_backoff_.ShouldRejectRequest()) {
      *response = protocol::SessionManager::OVERLOAD;
      return;
    }
  
    // We treat each incoming connection as a failure to authenticate,
    // and clear the backoff when a connection successfully
    // authenticates. This allows the backoff to protect from parallel
    // connection attempts as well as sequential ones.
    login_backoff_.InformOfRequest(false);
  
    protocol::SessionConfig config;
    if (!protocol_config_->Select(session->candidate_config(), &config)) {
      LOG(WARNING) << "Rejecting connection from " << session->jid()
                   << " because no compatible configuration has been found.";
      *response = protocol::SessionManager::INCOMPATIBLE;
      return;
    }
  
    session->set_config(config);
  
    *response = protocol::SessionManager::ACCEPT;
  
    LOG(INFO) << "Client connected: " << session->jid();
  
    // Create a client object.
    scoped_ptr<protocol::ConnectionToClient> connection(
        new protocol::ConnectionToClient(session));
    ClientSession* client = new ClientSession(
        this,
        audio_task_runner_,
        input_task_runner_,
        video_capture_task_runner_,
        video_encode_task_runner_,
        network_task_runner_,
        ui_task_runner_,
        connection.Pass(),
        desktop_environment_factory_,
        max_session_duration_,
        pairing_registry_);
    clients_.push_back(client);
  }
  
  void ChromotingHost::set_protocol_config(
      scoped_ptr<protocol::CandidateSessionConfig> config) {
    DCHECK(CalledOnValidThread());
    DCHECK(config.get());
    DCHECK(!started_);
    protocol_config_ = config.Pass();
  }
  
  void ChromotingHost::DisconnectAllClients() {
    DCHECK(CalledOnValidThread());
  
    while (!clients_.empty()) {
      size_t size = clients_.size();
      clients_.front()->DisconnectSession();
      CHECK_EQ(clients_.size(), size - 1);
    }
  }
  
  }  // namespace remoting
  