blob: 6066f950ad1f333289173aa85cd558bc03937012 [file] [log] [blame]
// Copyright 2014 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/ios/bridge/client_instance.h"
#include "base/bind.h"
#include "base/logging.h"
#include "base/synchronization/waitable_event.h"
#include "net/socket/client_socket_factory.h"
#include "remoting/base/url_request_context.h"
#include "remoting/client/audio_player.h"
#include "remoting/client/plugin/delegating_signal_strategy.h"
#include "remoting/ios/bridge/client_proxy.h"
#include "remoting/jingle_glue/chromium_port_allocator.h"
#include "remoting/protocol/host_stub.h"
#include "remoting/protocol/libjingle_transport_factory.h"
namespace {
const char* const kXmppServer = "talk.google.com";
const int kXmppPort = 5222;
const bool kXmppUseTls = true;
void DoNothing() {}
} // namespace
namespace remoting {
ClientInstance::ClientInstance(const base::WeakPtr<ClientProxy>& proxy,
const std::string& username,
const std::string& auth_token,
const std::string& host_jid,
const std::string& host_id,
const std::string& host_pubkey,
const std::string& pairing_id,
const std::string& pairing_secret)
: proxyToClient_(proxy), host_id_(host_id), create_pairing_(false) {
if (!base::MessageLoop::current()) {
VLOG(1) << "Starting main message loop";
ui_loop_ = new base::MessageLoopForUI();
ui_loop_->Attach();
} else {
VLOG(1) << "Using existing main message loop";
ui_loop_ = base::MessageLoopForUI::current();
}
VLOG(1) << "Spawning additional threads";
// |ui_loop_| runs on the main thread, so |ui_task_runner_| will run on the
// main thread. We can not kill the main thread when the message loop becomes
// idle so the callback function does nothing (as opposed to the typical
// base::MessageLoop::QuitClosure())
ui_task_runner_ = new AutoThreadTaskRunner(ui_loop_->message_loop_proxy(),
base::Bind(&::DoNothing));
network_task_runner_ = AutoThread::CreateWithType(
"native_net", ui_task_runner_, base::MessageLoop::TYPE_IO);
url_requester_ = new URLRequestContextGetter(network_task_runner_);
client_context_.reset(new ClientContext(network_task_runner_));
DCHECK(ui_task_runner_->BelongsToCurrentThread());
// Initialize XMPP config.
xmpp_config_.host = kXmppServer;
xmpp_config_.port = kXmppPort;
xmpp_config_.use_tls = kXmppUseTls;
xmpp_config_.username = username;
xmpp_config_.auth_token = auth_token;
xmpp_config_.auth_service = "oauth2";
// Initialize ClientConfig.
client_config_.host_jid = host_jid;
client_config_.host_public_key = host_pubkey;
client_config_.authentication_tag = host_id_;
client_config_.client_pairing_id = pairing_id;
client_config_.client_paired_secret = pairing_secret;
client_config_.authentication_methods.push_back(
protocol::AuthenticationMethod::FromString("spake2_pair"));
client_config_.authentication_methods.push_back(
protocol::AuthenticationMethod::FromString("spake2_hmac"));
client_config_.authentication_methods.push_back(
protocol::AuthenticationMethod::FromString("spake2_plain"));
}
ClientInstance::~ClientInstance() {}
void ClientInstance::Start() {
DCHECK(ui_task_runner_->BelongsToCurrentThread());
// Creates a reference to |this|, so don't want to bind during constructor
client_config_.fetch_secret_callback =
base::Bind(&ClientInstance::FetchSecret, this);
view_.reset(new FrameConsumerBridge(
base::Bind(&ClientProxy::RedrawCanvas, proxyToClient_)));
// |consumer_proxy| must be created on the UI thread to proxy calls from the
// network or decode thread to the UI thread, but ownership will belong to a
// SoftwareVideoRenderer which runs on the network thread.
scoped_refptr<FrameConsumerProxy> consumer_proxy =
new FrameConsumerProxy(ui_task_runner_, view_->AsWeakPtr());
// Post a task to start connection
base::WaitableEvent done_event(true, false);
network_task_runner_->PostTask(
FROM_HERE,
base::Bind(&ClientInstance::ConnectToHostOnNetworkThread,
this,
consumer_proxy,
base::Bind(&base::WaitableEvent::Signal,
base::Unretained(&done_event))));
// Wait until initialization completes before continuing
done_event.Wait();
}
void ClientInstance::Cleanup() {
DCHECK(ui_task_runner_->BelongsToCurrentThread());
client_config_.fetch_secret_callback.Reset(); // Release ref to this
// |view_| must be destroyed on the UI thread before the producer is gone.
view_.reset();
base::WaitableEvent done_event(true, false);
network_task_runner_->PostTask(
FROM_HERE,
base::Bind(&ClientInstance::DisconnectFromHostOnNetworkThread,
this,
base::Bind(&base::WaitableEvent::Signal,
base::Unretained(&done_event))));
// Wait until we are fully disconnected before continuing
done_event.Wait();
}
// HOST attempts to continue automatically with previously supplied credentials,
// if it can't it requests the user's PIN.
void ClientInstance::FetchSecret(
bool pairable,
const protocol::SecretFetchedCallback& callback) {
if (!ui_task_runner_->BelongsToCurrentThread()) {
ui_task_runner_->PostTask(
FROM_HERE,
base::Bind(&ClientInstance::FetchSecret, this, pairable, callback));
return;
}
pin_callback_ = callback;
if (proxyToClient_) {
if (!client_config_.client_pairing_id.empty()) {
// We attempted to connect using an existing pairing that was rejected.
// Unless we forget about the stale credentials, we'll continue trying
// them.
VLOG(1) << "Deleting rejected pairing credentials";
proxyToClient_->CommitPairingCredentials(host_id_, "", "");
}
proxyToClient_->DisplayAuthenticationPrompt(pairable);
}
}
void ClientInstance::ProvideSecret(const std::string& pin,
bool create_pairing) {
DCHECK(ui_task_runner_->BelongsToCurrentThread());
create_pairing_ = create_pairing;
// Before this function can complete, FetchSecret must be called
DCHECK(!pin_callback_.is_null());
network_task_runner_->PostTask(FROM_HERE, base::Bind(pin_callback_, pin));
}
void ClientInstance::PerformMouseAction(
const webrtc::DesktopVector& position,
const webrtc::DesktopVector& wheel_delta,
int /* protocol::MouseEvent_MouseButton */ whichButton,
bool button_down) {
if (!network_task_runner_->BelongsToCurrentThread()) {
network_task_runner_->PostTask(
FROM_HERE,
base::Bind(&ClientInstance::PerformMouseAction,
this,
position,
wheel_delta,
whichButton,
button_down));
return;
}
protocol::MouseEvent_MouseButton mButton;
// Button must be within the bounds of the MouseEvent_MouseButton enum.
switch (whichButton) {
case protocol::MouseEvent_MouseButton::MouseEvent_MouseButton_BUTTON_LEFT:
mButton =
protocol::MouseEvent_MouseButton::MouseEvent_MouseButton_BUTTON_LEFT;
break;
case protocol::MouseEvent_MouseButton::MouseEvent_MouseButton_BUTTON_MAX:
mButton =
protocol::MouseEvent_MouseButton::MouseEvent_MouseButton_BUTTON_MAX;
break;
case protocol::MouseEvent_MouseButton::MouseEvent_MouseButton_BUTTON_MIDDLE:
mButton = protocol::MouseEvent_MouseButton::
MouseEvent_MouseButton_BUTTON_MIDDLE;
break;
case protocol::MouseEvent_MouseButton::MouseEvent_MouseButton_BUTTON_RIGHT:
mButton =
protocol::MouseEvent_MouseButton::MouseEvent_MouseButton_BUTTON_RIGHT;
break;
case protocol::MouseEvent_MouseButton::
MouseEvent_MouseButton_BUTTON_UNDEFINED:
mButton = protocol::MouseEvent_MouseButton::
MouseEvent_MouseButton_BUTTON_UNDEFINED;
break;
default:
LOG(FATAL) << "Invalid constant for MouseEvent_MouseButton";
mButton = protocol::MouseEvent_MouseButton::
MouseEvent_MouseButton_BUTTON_UNDEFINED;
break;
}
protocol::MouseEvent action;
action.set_x(position.x());
action.set_y(position.y());
action.set_wheel_delta_x(wheel_delta.x());
action.set_wheel_delta_y(wheel_delta.y());
action.set_button(mButton);
if (mButton != protocol::MouseEvent::BUTTON_UNDEFINED)
action.set_button_down(button_down);
connection_->input_stub()->InjectMouseEvent(action);
}
void ClientInstance::PerformKeyboardAction(int key_code, bool key_down) {
if (!network_task_runner_->BelongsToCurrentThread()) {
network_task_runner_->PostTask(
FROM_HERE,
base::Bind(
&ClientInstance::PerformKeyboardAction, this, key_code, key_down));
return;
}
protocol::KeyEvent action;
action.set_usb_keycode(key_code);
action.set_pressed(key_down);
connection_->input_stub()->InjectKeyEvent(action);
}
void ClientInstance::OnConnectionState(protocol::ConnectionToHost::State state,
protocol::ErrorCode error) {
if (!ui_task_runner_->BelongsToCurrentThread()) {
ui_task_runner_->PostTask(
FROM_HERE,
base::Bind(&ClientInstance::OnConnectionState, this, state, error));
return;
}
// TODO (aboone) This functionality is not scheduled for QA yet.
// if (create_pairing_ && state == protocol::ConnectionToHost::CONNECTED) {
// VLOG(1) << "Attempting to pair with host";
// protocol::PairingRequest request;
// request.set_client_name("iOS");
// connection_->host_stub()->RequestPairing(request);
// }
if (proxyToClient_)
proxyToClient_->ReportConnectionStatus(state, error);
}
void ClientInstance::OnConnectionReady(bool ready) {
// We ignore this message, since OnConnectionState tells us the same thing.
}
void ClientInstance::OnRouteChanged(const std::string& channel_name,
const protocol::TransportRoute& route) {
VLOG(1) << "Using " << protocol::TransportRoute::GetTypeString(route.type)
<< " connection for " << channel_name << " channel";
}
void ClientInstance::SetCapabilities(const std::string& capabilities) {
DCHECK(video_renderer_);
DCHECK(connection_);
DCHECK(connection_->state() == protocol::ConnectionToHost::CONNECTED);
video_renderer_->Initialize(connection_->config());
}
void ClientInstance::SetPairingResponse(
const protocol::PairingResponse& response) {
if (!ui_task_runner_->BelongsToCurrentThread()) {
ui_task_runner_->PostTask(
FROM_HERE,
base::Bind(&ClientInstance::SetPairingResponse, this, response));
return;
}
VLOG(1) << "Successfully established pairing with host";
if (proxyToClient_)
proxyToClient_->CommitPairingCredentials(
host_id_, response.client_id(), response.shared_secret());
}
void ClientInstance::DeliverHostMessage(
const protocol::ExtensionMessage& message) {
NOTIMPLEMENTED();
}
// Returning interface of protocol::ClipboardStub
protocol::ClipboardStub* ClientInstance::GetClipboardStub() { return this; }
// Returning interface of protocol::CursorShapeStub
protocol::CursorShapeStub* ClientInstance::GetCursorShapeStub() { return this; }
scoped_ptr<protocol::ThirdPartyClientAuthenticator::TokenFetcher>
ClientInstance::GetTokenFetcher(const std::string& host_public_key) {
// Returns null when third-party authentication is unsupported.
return scoped_ptr<protocol::ThirdPartyClientAuthenticator::TokenFetcher>();
}
void ClientInstance::InjectClipboardEvent(
const protocol::ClipboardEvent& event) {
NOTIMPLEMENTED();
}
void ClientInstance::SetCursorShape(const protocol::CursorShapeInfo& shape) {
if (!ui_task_runner_->BelongsToCurrentThread()) {
ui_task_runner_->PostTask(
FROM_HERE, base::Bind(&ClientInstance::SetCursorShape, this, shape));
return;
}
if (proxyToClient_)
proxyToClient_->UpdateCursorShape(shape);
}
void ClientInstance::ConnectToHostOnNetworkThread(
scoped_refptr<FrameConsumerProxy> consumer_proxy,
const base::Closure& done) {
DCHECK(network_task_runner_->BelongsToCurrentThread());
client_context_->Start();
video_renderer_.reset(
new SoftwareVideoRenderer(client_context_->main_task_runner(),
client_context_->decode_task_runner(),
consumer_proxy));
view_->Initialize(video_renderer_.get());
connection_.reset(new protocol::ConnectionToHost(true));
client_.reset(new ChromotingClient(client_config_,
client_context_.get(),
connection_.get(),
this,
video_renderer_.get(),
scoped_ptr<AudioPlayer>()));
signaling_.reset(
new XmppSignalStrategy(net::ClientSocketFactory::GetDefaultFactory(),
url_requester_,
xmpp_config_));
NetworkSettings network_settings(NetworkSettings::NAT_TRAVERSAL_ENABLED);
scoped_ptr<ChromiumPortAllocator> port_allocator(
ChromiumPortAllocator::Create(url_requester_, network_settings));
scoped_ptr<protocol::TransportFactory> transport_factory(
new protocol::LibjingleTransportFactory(
signaling_.get(),
port_allocator.PassAs<cricket::HttpPortAllocatorBase>(),
network_settings));
client_->Start(signaling_.get(), transport_factory.Pass());
if (!done.is_null())
done.Run();
}
void ClientInstance::DisconnectFromHostOnNetworkThread(
const base::Closure& done) {
DCHECK(network_task_runner_->BelongsToCurrentThread());
host_id_.clear();
// |client_| must be torn down before |signaling_|.
connection_.reset();
client_.reset();
signaling_.reset();
video_renderer_.reset();
client_context_->Stop();
if (!done.is_null())
done.Run();
}
} // namespace remoting