diff options
Diffstat (limited to 'remoting/ios/bridge/client_instance.cc')
-rw-r--r-- | remoting/ios/bridge/client_instance.cc | 397 |
1 files changed, 397 insertions, 0 deletions
diff --git a/remoting/ios/bridge/client_instance.cc b/remoting/ios/bridge/client_instance.cc new file mode 100644 index 0000000..6066f95 --- /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 |