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 |