| // 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 |