| Index: remoting/ios/bridge/client_instance.cc
|
| diff --git a/remoting/ios/bridge/client_instance.cc b/remoting/ios/bridge/client_instance.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..6066f950ad1f333289173aa85cd558bc03937012
|
| --- /dev/null
|
| +++ b/remoting/ios/bridge/client_instance.cc
|
| @@ -0,0 +1,397 @@
|
| +// 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
|
|
|