// 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& 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 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 ClientInstance::GetTokenFetcher(const std::string& host_public_key) { // Returns null when third-party authentication is unsupported. return scoped_ptr(); } 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 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())); signaling_.reset( new XmppSignalStrategy(net::ClientSocketFactory::GetDefaultFactory(), url_requester_, xmpp_config_)); NetworkSettings network_settings(NetworkSettings::NAT_TRAVERSAL_ENABLED); scoped_ptr port_allocator( ChromiumPortAllocator::Create(url_requester_, network_settings)); scoped_ptr transport_factory( new protocol::LibjingleTransportFactory( signaling_.get(), port_allocator.PassAs(), 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