blob: 8a62459e28c8a9dce09956cb6a27ee321fd88f57 [file] [log] [blame]
// Copyright (c) 2010 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 "base/stl_util-inl.h"
#include "base/task.h"
#include "build/build_config.h"
#include "remoting/base/constants.h"
#include "remoting/base/encoder.h"
#include "remoting/base/encoder_verbatim.h"
#include "remoting/base/encoder_vp8.h"
#include "remoting/base/encoder_zlib.h"
#include "remoting/host/chromoting_host_context.h"
#include "remoting/host/capturer.h"
#include "remoting/host/event_executor.h"
#include "remoting/host/host_config.h"
#include "remoting/host/session_manager.h"
#include "remoting/protocol/session_config.h"
#include "remoting/protocol/jingle_session_manager.h"
#include "remoting/protocol/connection_to_client.h"
using remoting::protocol::ConnectionToClient;
namespace remoting {
ChromotingHost::ChromotingHost(ChromotingHostContext* context,
MutableHostConfig* config,
Capturer* capturer,
EventExecutor* executor)
: context_(context),
config_(config),
capturer_(capturer),
executor_(executor),
state_(kInitial) {
}
ChromotingHost::~ChromotingHost() {
}
void ChromotingHost::Start(Task* shutdown_task) {
if (MessageLoop::current() != context_->main_message_loop()) {
context_->main_message_loop()->PostTask(
FROM_HERE,
NewRunnableMethod(this, &ChromotingHost::Start, shutdown_task));
return;
}
DCHECK(!jingle_client_);
DCHECK(shutdown_task);
// Make sure this object is not started.
{
AutoLock auto_lock(lock_);
if (state_ != kInitial)
return;
state_ = kStarted;
}
// Save the shutdown task.
shutdown_task_.reset(shutdown_task);
std::string xmpp_login;
std::string xmpp_auth_token;
if (!config_->GetString(kXmppLoginConfigPath, &xmpp_login) ||
!config_->GetString(kXmppAuthTokenConfigPath, &xmpp_auth_token)) {
LOG(ERROR) << "XMPP credentials are not defined in the config.";
return;
}
if (!access_verifier_.Init(config_))
return;
// Connect to the talk network with a JingleClient.
jingle_client_ = new JingleClient(context_->jingle_thread());
jingle_client_->Init(xmpp_login, xmpp_auth_token,
kChromotingTokenServiceName, this);
heartbeat_sender_ = new HeartbeatSender();
if (!heartbeat_sender_->Init(config_, jingle_client_.get())) {
LOG(ERROR) << "Failed to initialize HeartbeatSender.";
return;
}
}
// This method is called when we need to destroy the host process.
void ChromotingHost::Shutdown() {
if (MessageLoop::current() != context_->main_message_loop()) {
context_->main_message_loop()->PostTask(
FROM_HERE,
NewRunnableMethod(this, &ChromotingHost::Shutdown));
return;
}
// No-op if this object is not started yet.
{
AutoLock auto_lock(lock_);
if (state_ != kStarted) {
state_ = kStopped;
return;
}
state_ = kStopped;
}
// Tell the session to pause and then disconnect all clients.
if (session_.get()) {
session_->Pause();
session_->RemoveAllConnections();
}
// Disconnect the client.
if (connection_) {
connection_->Disconnect();
}
// Stop the heartbeat sender.
if (heartbeat_sender_) {
heartbeat_sender_->Stop();
}
// Stop chromotocol session manager.
if (session_manager_) {
session_manager_->Close(
NewRunnableMethod(this, &ChromotingHost::OnServerClosed));
}
// Disconnect from the talk network.
if (jingle_client_) {
jingle_client_->Close();
}
// Lastly call the shutdown task.
if (shutdown_task_.get()) {
shutdown_task_->Run();
}
}
// This method is called when a client connects.
void ChromotingHost::OnClientConnected(ConnectionToClient* connection) {
DCHECK_EQ(context_->main_message_loop(), MessageLoop::current());
// Create a new RecordSession if there was none.
if (!session_.get()) {
// Then we create a SessionManager passing the message loops that
// it should run on.
DCHECK(capturer_.get());
Encoder* encoder = CreateEncoder(connection->session()->config());
session_ = new SessionManager(context_->capture_message_loop(),
context_->encode_message_loop(),
context_->main_message_loop(),
capturer_.release(),
encoder);
}
// Immediately add the connection and start the session.
session_->AddConnection(connection);
session_->Start();
VLOG(1) << "Session manager started";
}
void ChromotingHost::OnClientDisconnected(ConnectionToClient* connection) {
DCHECK_EQ(context_->main_message_loop(), MessageLoop::current());
// Remove the connection from the session manager and pause the session.
// TODO(hclam): Pause only if the last connection disconnected.
if (session_.get()) {
session_->RemoveConnection(connection);
session_->Pause();
}
// Close the connection to connection just to be safe.
connection->Disconnect();
// Also remove reference to ConnectionToClient from this object.
connection_ = NULL;
}
////////////////////////////////////////////////////////////////////////////
// protocol::ConnectionToClient::EventHandler implementations
void ChromotingHost::HandleMessage(ConnectionToClient* connection,
ChromotingClientMessage* message) {
DCHECK_EQ(context_->main_message_loop(), MessageLoop::current());
// Delegate the messages to EventExecutor and delete the unhandled
// messages.
DCHECK(executor_.get());
executor_->HandleInputEvent(message);
}
void ChromotingHost::OnConnectionOpened(ConnectionToClient* connection) {
DCHECK_EQ(context_->main_message_loop(), MessageLoop::current());
// Completes the connection to the client.
VLOG(1) << "Connection to client established.";
OnClientConnected(connection_.get());
}
void ChromotingHost::OnConnectionClosed(ConnectionToClient* connection) {
DCHECK_EQ(context_->main_message_loop(), MessageLoop::current());
VLOG(1) << "Connection to client closed.";
OnClientDisconnected(connection_.get());
}
void ChromotingHost::OnConnectionFailed(ConnectionToClient* connection) {
DCHECK_EQ(context_->main_message_loop(), MessageLoop::current());
LOG(ERROR) << "Connection failed unexpectedly.";
OnClientDisconnected(connection_.get());
}
////////////////////////////////////////////////////////////////////////////
// JingleClient::Callback implementations
void ChromotingHost::OnStateChange(JingleClient* jingle_client,
JingleClient::State state) {
if (state == JingleClient::CONNECTED) {
DCHECK_EQ(jingle_client_.get(), jingle_client);
VLOG(1) << "Host connected as " << jingle_client->GetFullJid();
// Create and start session manager.
protocol::JingleSessionManager* server =
new protocol::JingleSessionManager(context_->jingle_thread());
// TODO(ajwong): Make this a command switch when we're more stable.
server->set_allow_local_ips(true);
server->Init(jingle_client->GetFullJid(),
jingle_client->session_manager(),
NewCallback(this, &ChromotingHost::OnNewClientSession));
session_manager_ = server;
// Start heartbeating.
heartbeat_sender_->Start();
} else if (state == JingleClient::CLOSED) {
VLOG(1) << "Host disconnected from talk network.";
// Stop heartbeating.
heartbeat_sender_->Stop();
// TODO(sergeyu): We should try reconnecting here instead of terminating
// the host.
Shutdown();
}
}
void ChromotingHost::OnNewClientSession(
protocol::Session* session,
protocol::SessionManager::IncomingSessionResponse* response) {
AutoLock auto_lock(lock_);
// TODO(hclam): Allow multiple clients to connect to the host.
if (connection_.get() || state_ != kStarted) {
*response = protocol::SessionManager::DECLINE;
return;
}
// Check that the user has access to the host.
if (!access_verifier_.VerifyPermissions(session->jid())) {
*response = protocol::SessionManager::DECLINE;
return;
}
scoped_ptr<protocol::CandidateSessionConfig>
local_config(protocol::CandidateSessionConfig::CreateDefault());
local_config->SetInitialResolution(
protocol::ScreenResolution(capturer_->width(), capturer_->height()));
// TODO(sergeyu): Respect resolution requested by the client if supported.
protocol::SessionConfig* config = local_config->Select(
session->candidate_config(), true /* force_host_resolution */);
if (!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;
VLOG(1) << "Client connected: " << session->jid();
// If we accept the connected then create a client object and set the
// callback.
connection_ = new ConnectionToClient(context_->main_message_loop(), this);
connection_->Init(session);
}
void ChromotingHost::OnServerClosed() {
// Don't need to do anything here.
}
// TODO(sergeyu): Move this to SessionManager?
Encoder* ChromotingHost::CreateEncoder(const protocol::SessionConfig* config) {
const protocol::ChannelConfig& video_config = config->video_config();
if (video_config.codec == protocol::ChannelConfig::CODEC_VERBATIM) {
return new remoting::EncoderVerbatim();
} else if (video_config.codec == protocol::ChannelConfig::CODEC_ZIP) {
return new remoting::EncoderZlib();
}
// TODO(sergeyu): Enable VP8 on ARM builds.
#if !defined(ARCH_CPU_ARM_FAMILY)
else if (video_config.codec == protocol::ChannelConfig::CODEC_VP8) {
return new remoting::EncoderVp8();
}
#endif
return NULL;
}
} // namespace remoting