// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "remoting/ios/session/remoting_client.h"
#include <memory>
#import <MaterialComponents/MaterialSnackbar.h>
#include "base/functional/bind.h"
#include "base/logging.h"
#import "base/memory/raw_ptr.h"
#include "base/strings/sys_string_conversions.h"
#include "remoting/client/audio/audio_playback_stream.h"
#include "remoting/client/chromoting_client_runtime.h"
#include "remoting/client/chromoting_session.h"
#include "remoting/client/connect_to_host_info.h"
#include "remoting/client/gesture_interpreter.h"
#include "remoting/client/input/keyboard_interpreter.h"
#import "remoting/ios/audio/audio_playback_sink_ios.h"
#import "remoting/ios/display/gl_display_handler.h"
#import "remoting/ios/domain/client_session_details.h"
#import "remoting/ios/domain/host_info.h"
#import "remoting/ios/facade/remoting_authentication.h"
#import "remoting/ios/facade/remoting_service.h"
#import "remoting/ios/persistence/host_pairing_info.h"
#import "remoting/ios/persistence/remoting_preferences.h"
#include "remoting/ios/session/remoting_client_session_delegate.h"
#include "remoting/protocol/session.h"
#include "remoting/protocol/video_renderer.h"
NSString* const kHostSessionStatusChanged = @"kHostSessionStatusChanged";
NSString* const kHostSessionPinProvided = @"kHostSessionPinProvided";
NSString* const kSessionDetails = @"kSessionDetails";
NSString* const kSessionSupportsPairing = @"kSessionSupportsPairing";
NSString* const kSessionStateErrorCode = @"kSessionStateErrorCode";
NSString* const kHostSessionCreatePairing = @"kHostSessionCreatePairing";
NSString* const kHostSessionHostName = @"kHostSessionHostName";
NSString* const kHostSessionPin = @"kHostSessionPin";
static std::string GetCurrentUserId() {
return base::SysNSStringToUTF8(
RemotingService.instance.authentication.user.userId);
}
// Block doesn't work with rvalue passing. This function helps exposing the data
// to the block.
static void ResolveFeedbackDataCallback(
void (^callback)(const remoting::FeedbackData&),
std::unique_ptr<remoting::FeedbackData> data) {
DCHECK(remoting::ChromotingClientRuntime::GetInstance()
->ui_task_runner()
->BelongsToCurrentThread());
remoting::FeedbackData* raw_data = data.get();
callback(*raw_data);
}
@interface RemotingClient () {
raw_ptr<remoting::ChromotingClientRuntime> _runtime;
std::unique_ptr<remoting::RemotingClientSessionDelegate> _sessionDelegate;
ClientSessionDetails* _sessionDetails;
remoting::protocol::SecretFetchedCallback _secretFetchedCallback;
remoting::GestureInterpreter _gestureInterpreter;
remoting::KeyboardInterpreter _keyboardInterpreter;
// _session is valid only when the session is connected.
std::unique_ptr<remoting::ChromotingSession> _session;
}
@end
@implementation RemotingClient
@synthesize displayHandler = _displayHandler;
- (instancetype)init {
self = [super init];
if (self) {
_runtime = remoting::ChromotingClientRuntime::GetInstance();
_sessionDelegate.reset(new remoting::RemotingClientSessionDelegate(self));
_sessionDetails = [[ClientSessionDetails alloc] init];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(hostSessionPinProvided:)
name:kHostSessionPinProvided
object:nil];
}
return self;
}
- (void)dealloc {
[self disconnectFromHost];
}
- (void)connectToHost:(HostInfo*)hostInfo
username:(NSString*)username
accessToken:(NSString*)accessToken
entryPoint:(remoting::ChromotingEvent::SessionEntryPoint)entryPoint {
DCHECK(_runtime->ui_task_runner()->BelongsToCurrentThread());
DCHECK(hostInfo);
DCHECK(hostInfo.hostId);
DCHECK(hostInfo.publicKey);
_sessionDetails.hostInfo = hostInfo;
remoting::ConnectToHostInfo info;
info.username = base::SysNSStringToUTF8(username);
info.auth_token = base::SysNSStringToUTF8(accessToken);
info.host_jid = base::SysNSStringToUTF8(hostInfo.jabberId);
info.host_ftl_id = base::SysNSStringToUTF8(hostInfo.ftlId);
info.host_id = base::SysNSStringToUTF8(hostInfo.hostId);
info.host_pubkey = base::SysNSStringToUTF8(hostInfo.publicKey);
info.host_os = base::SysNSStringToUTF8(hostInfo.hostOs);
info.host_os_version = base::SysNSStringToUTF8(hostInfo.hostOsVersion);
info.host_version = base::SysNSStringToUTF8(hostInfo.hostVersion);
remoting::HostPairingInfo pairing = remoting::HostPairingInfo::GetPairingInfo(
GetCurrentUserId(), info.host_id);
info.pairing_id = pairing.pairing_id();
info.pairing_secret = pairing.pairing_secret();
info.session_entry_point = entryPoint;
info.capabilities = "";
if ([RemotingPreferences.instance boolForFlag:RemotingFlagUseWebRTC]) {
info.flags = "useWebrtc";
[MDCSnackbarManager.defaultManager
showMessage:[MDCSnackbarMessage messageWithText:@"Using WebRTC"]];
}
auto audioStream = std::make_unique<remoting::AudioPlaybackStream>(
std::make_unique<remoting::AudioPlaybackSinkIos>(),
_runtime->audio_task_runner());
_displayHandler = [[GlDisplayHandler alloc] init];
_displayHandler.delegate = self;
_session = std::make_unique<remoting::ChromotingSession>(
_sessionDelegate->GetWeakPtr(), [_displayHandler createCursorShapeStub],
[_displayHandler createVideoRenderer], std::move(audioStream), info);
_gestureInterpreter.SetContext(_displayHandler.rendererProxy, _session.get());
_keyboardInterpreter.SetContext(_session.get());
}
- (void)disconnectFromHost {
_session.reset();
_displayHandler = nil;
_gestureInterpreter.SetContext(nullptr, nullptr);
_keyboardInterpreter.SetContext(nullptr);
}
#pragma mark - Eventing
- (void)hostSessionPinProvided:(NSNotification*)notification {
NSString* pin = [[notification userInfo] objectForKey:kHostSessionPin];
NSString* name = UIDevice.currentDevice.name;
BOOL shouldCreatePairing = [[[notification userInfo]
objectForKey:kHostSessionCreatePairing] boolValue];
if (_session && shouldCreatePairing) {
_session->RequestPairing(base::SysNSStringToUTF8(name));
}
if (_secretFetchedCallback) {
std::move(_secretFetchedCallback).Run(base::SysNSStringToUTF8(pin));
}
}
#pragma mark - Properties
- (HostInfo*)hostInfo {
return _sessionDetails.hostInfo;
}
- (remoting::GestureInterpreter*)gestureInterpreter {
return &_gestureInterpreter;
}
- (remoting::KeyboardInterpreter*)keyboardInterpreter {
return &_keyboardInterpreter;
}
#pragma mark - ChromotingSession::Delegate
- (void)onConnectionState:(remoting::protocol::ConnectionToHost::State)state
error:(remoting::protocol::ErrorCode)error {
switch (state) {
case remoting::protocol::ConnectionToHost::INITIALIZING:
_sessionDetails.state = SessionInitializing;
break;
case remoting::protocol::ConnectionToHost::CONNECTING:
_sessionDetails.state = SessionConnecting;
break;
case remoting::protocol::ConnectionToHost::AUTHENTICATED:
_sessionDetails.state = SessionAuthenticated;
break;
case remoting::protocol::ConnectionToHost::CONNECTED:
_sessionDetails.state = SessionConnected;
break;
case remoting::protocol::ConnectionToHost::FAILED:
_sessionDetails.state = SessionFailed;
break;
case remoting::protocol::ConnectionToHost::CLOSED:
_sessionDetails.state = SessionClosed;
[self disconnectFromHost];
break;
default:
LOG(ERROR) << "onConnectionState, unknown state: " << state;
}
switch (error) {
case remoting::protocol::ErrorCode::OK:
_sessionDetails.error = SessionErrorOk;
break;
case remoting::protocol::ErrorCode::PEER_IS_OFFLINE:
_sessionDetails.error = SessionErrorPeerIsOffline;
break;
case remoting::protocol::ErrorCode::SESSION_REJECTED:
_sessionDetails.error = SessionErrorSessionRejected;
break;
case remoting::protocol::ErrorCode::INCOMPATIBLE_PROTOCOL:
_sessionDetails.error = SessionErrorIncompatibleProtocol;
break;
case remoting::protocol::ErrorCode::AUTHENTICATION_FAILED:
if (_sessionDetails.error != SessionErrorThirdPartyAuthNotSupported) {
_sessionDetails.error = SessionErrorAuthenticationFailed;
}
break;
case remoting::protocol::ErrorCode::INVALID_ACCOUNT:
_sessionDetails.error = SessionErrorInvalidAccount;
break;
case remoting::protocol::ErrorCode::CHANNEL_CONNECTION_ERROR:
_sessionDetails.error = SessionErrorChannelConnectionError;
break;
case remoting::protocol::ErrorCode::SIGNALING_ERROR:
_sessionDetails.error = SessionErrorSignalingError;
break;
case remoting::protocol::ErrorCode::SIGNALING_TIMEOUT:
_sessionDetails.error = SessionErrorSignalingTimeout;
break;
case remoting::protocol::ErrorCode::HOST_OVERLOAD:
_sessionDetails.error = SessionErrorHostOverload;
break;
case remoting::protocol::ErrorCode::MAX_SESSION_LENGTH:
_sessionDetails.error = SessionErrorMaxSessionLength;
break;
case remoting::protocol::ErrorCode::HOST_CONFIGURATION_ERROR:
_sessionDetails.error = SessionErrorHostConfigurationError;
break;
default:
_sessionDetails.error = SessionErrorUnknownError;
break;
}
[[NSNotificationCenter defaultCenter]
postNotificationName:kHostSessionStatusChanged
object:self
userInfo:[NSDictionary dictionaryWithObject:_sessionDetails
forKey:kSessionDetails]];
}
- (void)commitPairingCredentialsForHost:(NSString*)host
id:(NSString*)pairingId
secret:(NSString*)secret {
remoting::HostPairingInfo info = remoting::HostPairingInfo::GetPairingInfo(
GetCurrentUserId(), base::SysNSStringToUTF8(host));
std::string utf8PairingId = base::SysNSStringToUTF8(pairingId);
std::string utf8Secret = base::SysNSStringToUTF8(secret);
if (utf8PairingId == info.pairing_id() &&
utf8Secret == info.pairing_secret()) {
// The pairing has not been changed so we can return early.
return;
}
info.set_pairing_id(utf8PairingId);
info.set_pairing_secret(utf8Secret);
info.Save();
}
- (void)
fetchSecretWithPairingSupported:(BOOL)pairingSupported
callback:
(const remoting::protocol::SecretFetchedCallback&)
secretFetchedCallback {
_secretFetchedCallback = secretFetchedCallback;
_sessionDetails.state = SessionPinPrompt;
// Clear pairing credentials if they exist (which are no longer valid).
[self commitPairingCredentialsForHost:self.hostInfo.hostId id:@"" secret:@""];
[NSNotificationCenter.defaultCenter
postNotificationName:kHostSessionStatusChanged
object:self
userInfo:@{
kSessionDetails : _sessionDetails,
kSessionSupportsPairing : @(pairingSupported),
}];
}
- (void)setCapabilities:(NSString*)capabilities {
DCHECK(capabilities.length == 0) << "No capability has been implemented on "
<< "iOS yet";
}
- (void)handleExtensionMessageOfType:(NSString*)type
message:(NSString*)message {
NOTREACHED_IN_MIGRATION() << "handleExtensionMessageOfType is unimplemented. "
<< type << ":" << message;
}
- (void)setHostResolution:(CGSize)resolutionPixels scale:(int)scale {
if (_session) {
_session->SendClientResolution(resolutionPixels.width,
resolutionPixels.height, scale);
}
}
- (void)setVideoChannelEnabled:(BOOL)enabled {
if (_session) {
_session->EnableVideoChannel(enabled);
}
}
- (void)createFeedbackDataWithCallback:
(void (^)(const remoting::FeedbackData&))callback {
if (!_session) {
// Session has never been connected. Returns an empty data.
callback(remoting::FeedbackData());
return;
}
_session->GetFeedbackData(
base::BindOnce(&ResolveFeedbackDataCallback, callback));
}
#pragma mark - GlDisplayHandlerDelegate
- (void)canvasSizeChanged:(CGSize)size {
_gestureInterpreter.OnDesktopSizeChanged(size.width, size.height);
}
- (void)rendererTicked {
_gestureInterpreter.ProcessAnimations();
}
@end