blob: e0b94508ee8255c35c554e1fc20cf8e01cbc13f5 [file] [log] [blame]
// Copyright (c) 2011 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/protocol/jingle_session.h"
#include "base/bind.h"
#include "base/location.h"
#include "base/message_loop_proxy.h"
#include "base/rand_util.h"
#include "base/stl_util.h"
#include "crypto/hmac.h"
#include "crypto/rsa_private_key.h"
#include "net/base/net_errors.h"
#include "net/socket/stream_socket.h"
#include "remoting/base/constants.h"
#include "remoting/protocol/jingle_datagram_connector.h"
#include "remoting/protocol/jingle_session_manager.h"
#include "remoting/protocol/jingle_stream_connector.h"
#include "third_party/libjingle/source/talk/base/thread.h"
#include "third_party/libjingle/source/talk/p2p/base/p2ptransportchannel.h"
#include "third_party/libjingle/source/talk/p2p/base/session.h"
#include "third_party/libjingle/source/talk/p2p/base/transport.h"
using cricket::BaseSession;
namespace remoting {
namespace protocol {
// static
JingleSession* JingleSession::CreateClientSession(
JingleSessionManager* manager, const std::string& host_public_key) {
return new JingleSession(manager, "", NULL, host_public_key);
}
// static
JingleSession* JingleSession::CreateServerSession(
JingleSessionManager* manager,
const std::string& certificate,
crypto::RSAPrivateKey* key) {
return new JingleSession(manager, certificate, key, "");
}
JingleSession::JingleSession(
JingleSessionManager* jingle_session_manager,
const std::string& local_cert,
crypto::RSAPrivateKey* local_private_key,
const std::string& peer_public_key)
: jingle_session_manager_(jingle_session_manager),
local_cert_(local_cert),
state_(INITIALIZING),
error_(OK),
closing_(false),
cricket_session_(NULL),
config_set_(false),
ALLOW_THIS_IN_INITIALIZER_LIST(task_factory_(this)) {
// TODO(hclam): Need a better way to clone a key.
if (local_private_key) {
std::vector<uint8> key_bytes;
CHECK(local_private_key->ExportPrivateKey(&key_bytes));
local_private_key_.reset(
crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(key_bytes));
CHECK(local_private_key_.get());
}
}
JingleSession::~JingleSession() {
// Reset the callback so that it's not called from Close().
state_change_callback_.Reset();
Close();
jingle_session_manager_->SessionDestroyed(this);
}
void JingleSession::Init(cricket::Session* cricket_session) {
DCHECK(CalledOnValidThread());
cricket_session_ = cricket_session;
jid_ = cricket_session_->remote_name();
cricket_session_->SignalState.connect(
this, &JingleSession::OnSessionState);
cricket_session_->SignalError.connect(
this, &JingleSession::OnSessionError);
}
void JingleSession::CloseInternal(int result, Error error) {
DCHECK(CalledOnValidThread());
if (state_ != FAILED && state_ != CLOSED && !closing_) {
closing_ = true;
control_channel_socket_.reset();
event_channel_socket_.reset();
STLDeleteContainerPairSecondPointers(channel_connectors_.begin(),
channel_connectors_.end());
// Tear down the cricket session, including the cricket transport channels.
if (cricket_session_) {
std::string reason;
switch (error) {
case OK:
reason = cricket::STR_TERMINATE_SUCCESS;
break;
case SESSION_REJECTED:
reason = cricket::STR_TERMINATE_DECLINE;
break;
case INCOMPATIBLE_PROTOCOL:
reason = cricket::STR_TERMINATE_INCOMPATIBLE_PARAMETERS;
break;
default:
reason = cricket::STR_TERMINATE_ERROR;
}
cricket_session_->TerminateWithReason(reason);
cricket_session_->SignalState.disconnect(this);
}
error_ = error;
// Inform the StateChangeCallback, so calling code knows not to
// touch any channels. Needs to be done in the end because the
// session may be deleted in response to this event.
if (error != OK) {
SetState(FAILED);
} else {
SetState(CLOSED);
}
}
}
bool JingleSession::HasSession(cricket::Session* cricket_session) {
DCHECK(CalledOnValidThread());
return cricket_session_ == cricket_session;
}
cricket::Session* JingleSession::ReleaseSession() {
DCHECK(CalledOnValidThread());
// Session may be destroyed only after it is closed.
DCHECK(state_ == FAILED || state_ == CLOSED);
cricket::Session* session = cricket_session_;
if (cricket_session_)
cricket_session_->SignalState.disconnect(this);
cricket_session_ = NULL;
return session;
}
void JingleSession::SetStateChangeCallback(
const StateChangeCallback& callback) {
DCHECK(CalledOnValidThread());
DCHECK(!callback.is_null());
state_change_callback_ = callback;
}
Session::Error JingleSession::error() {
DCHECK(CalledOnValidThread());
return error_;
}
void JingleSession::CreateStreamChannel(
const std::string& name, const StreamChannelCallback& callback) {
DCHECK(CalledOnValidThread());
AddChannelConnector(
name, new JingleStreamConnector(this, name, callback));
}
void JingleSession::CreateDatagramChannel(
const std::string& name, const DatagramChannelCallback& callback) {
DCHECK(CalledOnValidThread());
AddChannelConnector(
name, new JingleDatagramConnector(this, name, callback));
}
void JingleSession::CancelChannelCreation(const std::string& name) {
ChannelConnectorsMap::iterator it = channel_connectors_.find(name);
if (it != channel_connectors_.end()) {
delete it->second;
channel_connectors_.erase(it);
}
}
net::Socket* JingleSession::control_channel() {
DCHECK(CalledOnValidThread());
return control_channel_socket_.get();
}
net::Socket* JingleSession::event_channel() {
DCHECK(CalledOnValidThread());
return event_channel_socket_.get();
}
const std::string& JingleSession::jid() {
DCHECK(CalledOnValidThread());
return jid_;
}
const CandidateSessionConfig* JingleSession::candidate_config() {
DCHECK(CalledOnValidThread());
DCHECK(candidate_config_.get());
return candidate_config_.get();
}
void JingleSession::set_candidate_config(
const CandidateSessionConfig* candidate_config) {
DCHECK(CalledOnValidThread());
DCHECK(!candidate_config_.get());
DCHECK(candidate_config);
candidate_config_.reset(candidate_config);
}
const std::string& JingleSession::local_certificate() const {
DCHECK(CalledOnValidThread());
return local_cert_;
}
const SessionConfig& JingleSession::config() {
DCHECK(CalledOnValidThread());
DCHECK(config_set_);
return config_;
}
void JingleSession::set_config(const SessionConfig& config) {
DCHECK(CalledOnValidThread());
DCHECK(!config_set_);
config_ = config;
config_set_ = true;
}
const std::string& JingleSession::initiator_token() {
DCHECK(CalledOnValidThread());
return initiator_token_;
}
void JingleSession::set_initiator_token(const std::string& initiator_token) {
DCHECK(CalledOnValidThread());
initiator_token_ = initiator_token;
}
const std::string& JingleSession::receiver_token() {
DCHECK(CalledOnValidThread());
return receiver_token_;
}
void JingleSession::set_receiver_token(const std::string& receiver_token) {
DCHECK(CalledOnValidThread());
receiver_token_ = receiver_token;
}
void JingleSession::set_shared_secret(const std::string& secret) {
DCHECK(CalledOnValidThread());
shared_secret_ = secret;
}
const std::string& JingleSession::shared_secret() {
DCHECK(CalledOnValidThread());
return shared_secret_;
}
void JingleSession::Close() {
DCHECK(CalledOnValidThread());
CloseInternal(net::ERR_CONNECTION_CLOSED, OK);
}
void JingleSession::OnSessionState(
BaseSession* session, BaseSession::State state) {
DCHECK(CalledOnValidThread());
DCHECK_EQ(cricket_session_, session);
if (state_ == FAILED || state_ == CLOSED) {
// Don't do anything if we already closed.
return;
}
switch (state) {
case cricket::Session::STATE_SENTINITIATE:
case cricket::Session::STATE_RECEIVEDINITIATE:
OnInitiate();
break;
case cricket::Session::STATE_SENTACCEPT:
case cricket::Session::STATE_RECEIVEDACCEPT:
OnAccept();
break;
case cricket::Session::STATE_SENTTERMINATE:
case cricket::Session::STATE_RECEIVEDTERMINATE:
case cricket::Session::STATE_SENTREJECT:
case cricket::Session::STATE_RECEIVEDREJECT:
OnTerminate();
break;
case cricket::Session::STATE_DEINIT:
// Close() must have been called before this.
NOTREACHED();
break;
default:
// We don't care about other steates.
break;
}
}
void JingleSession::OnSessionError(
BaseSession* session, BaseSession::Error error) {
DCHECK(CalledOnValidThread());
DCHECK_EQ(cricket_session_, session);
if (error != cricket::Session::ERROR_NONE) {
// TODO(sergeyu): Report different errors depending on |error|.
CloseInternal(net::ERR_CONNECTION_ABORTED, CHANNEL_CONNECTION_ERROR);
}
}
void JingleSession::OnInitiate() {
DCHECK(CalledOnValidThread());
jid_ = cricket_session_->remote_name();
if (!cricket_session_->initiator()) {
const protocol::ContentDescription* content_description =
static_cast<const protocol::ContentDescription*>(
GetContentInfo()->description);
CHECK(content_description);
}
if (cricket_session_->initiator()) {
// Set state to CONNECTING if this is an outgoing message. We need
// to post this task because channel creation works only after we
// return from this method. This is because
// JingleChannelConnector::Connect() needs to call
// set_incoming_only() on P2PTransportChannel, but
// P2PTransportChannel is created only after we return from this
// method.
// TODO(sergeyu): Add set_incoming_only() in TransportChannelProxy.
jingle_session_manager_->message_loop_->PostTask(
FROM_HERE, task_factory_.NewRunnableMethod(
&JingleSession::SetState, CONNECTING));
} else {
jingle_session_manager_->message_loop_->PostTask(
FROM_HERE, task_factory_.NewRunnableMethod(
&JingleSession::AcceptConnection));
}
}
bool JingleSession::InitializeConfigFromDescription(
const cricket::SessionDescription* description) {
// We should only be called after ParseContent has succeeded, in which
// case there will always be a Chromoting session configuration.
const cricket::ContentInfo* content =
description->FirstContentByType(kChromotingXmlNamespace);
CHECK(content);
const protocol::ContentDescription* content_description =
static_cast<const protocol::ContentDescription*>(content->description);
CHECK(content_description);
remote_cert_ = content_description->certificate();
if (remote_cert_.empty()) {
LOG(ERROR) << "Connection response does not specify certificate";
return false;
}
SessionConfig config;
if (!content_description->config()->GetFinalConfig(&config)) {
LOG(ERROR) << "Connection response does not specify configuration";
return false;
}
if (!candidate_config()->IsSupported(config)) {
LOG(ERROR) << "Connection response specifies an invalid configuration";
return false;
}
set_config(config);
return true;
}
void JingleSession::OnAccept() {
DCHECK(CalledOnValidThread());
// If we initiated the session, store the candidate configuration that the
// host responded with, to refer to later.
if (cricket_session_->initiator()) {
if (!InitializeConfigFromDescription(
cricket_session_->remote_description())) {
CloseInternal(net::ERR_CONNECTION_FAILED, INCOMPATIBLE_PROTOCOL);
return;
}
}
CreateChannels();
SetState(CONNECTED);
}
void JingleSession::OnTerminate() {
DCHECK(CalledOnValidThread());
CloseInternal(net::ERR_CONNECTION_ABORTED, OK);
}
void JingleSession::AcceptConnection() {
SetState(CONNECTING);
if (!jingle_session_manager_->AcceptConnection(this, cricket_session_)) {
Close();
// Release session so that JingleSessionManager::SessionDestroyed()
// doesn't try to call cricket::SessionManager::DestroySession() for it.
ReleaseSession();
delete this;
return;
}
}
void JingleSession::AddChannelConnector(
const std::string& name, JingleChannelConnector* connector) {
DCHECK(channel_connectors_.find(name) == channel_connectors_.end());
const std::string& content_name = GetContentInfo()->name;
cricket::TransportChannel* raw_channel =
cricket_session_->CreateChannel(content_name, name);
if (!jingle_session_manager_->allow_nat_traversal_ &&
!cricket_session_->initiator()) {
// Don't make outgoing connections from the host to client when
// NAT traversal is disabled.
raw_channel->GetP2PChannel()->set_incoming_only(true);
}
channel_connectors_[name] = connector;
connector->Connect(cricket_session_->initiator(), local_cert_,
remote_cert_, local_private_key_.get(), raw_channel);
// Workaround bug in libjingle - it doesn't connect channels if they
// are created after the session is accepted. See crbug.com/89384.
// TODO(sergeyu): Fix the bug and remove this line.
cricket_session_->GetTransport(content_name)->ConnectChannels();
}
void JingleSession::OnChannelConnectorFinished(
const std::string& name, JingleChannelConnector* connector) {
DCHECK(CalledOnValidThread());
DCHECK_EQ(channel_connectors_[name], connector);
channel_connectors_.erase(name);
}
void JingleSession::CreateChannels() {
CreateStreamChannel(
kControlChannelName,
base::Bind(&JingleSession::OnChannelConnected,
base::Unretained(this), &control_channel_socket_));
CreateStreamChannel(
kEventChannelName,
base::Bind(&JingleSession::OnChannelConnected,
base::Unretained(this), &event_channel_socket_));
}
void JingleSession::OnChannelConnected(
scoped_ptr<net::Socket>* socket_container,
net::StreamSocket* socket) {
if (!socket) {
LOG(ERROR) << "Failed to connect control or events channel. "
<< "Terminating connection";
CloseInternal(net::ERR_CONNECTION_CLOSED, CHANNEL_CONNECTION_ERROR);
return;
}
socket_container->reset(socket);
if (control_channel_socket_.get() && event_channel_socket_.get())
SetState(CONNECTED_CHANNELS);
}
const cricket::ContentInfo* JingleSession::GetContentInfo() const {
const cricket::SessionDescription* session_description;
// If we initiate the session, we get to specify the content name. When
// accepting one, the remote end specifies it.
if (cricket_session_->initiator()) {
session_description = cricket_session_->local_description();
} else {
session_description = cricket_session_->remote_description();
}
const cricket::ContentInfo* content =
session_description->FirstContentByType(kChromotingXmlNamespace);
CHECK(content);
return content;
}
void JingleSession::SetState(State new_state) {
DCHECK(CalledOnValidThread());
if (new_state != state_) {
DCHECK_NE(state_, CLOSED);
DCHECK_NE(state_, FAILED);
state_ = new_state;
if (!state_change_callback_.is_null())
state_change_callback_.Run(new_state);
}
}
} // namespace protocol
} // namespace remoting