blob: 03167a901c68fdd201ca88099aa25b5724451777 [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/pepper_session.h"
#include "base/bind.h"
#include "base/rand_util.h"
#include "base/stl_util.h"
#include "base/string_number_conversions.h"
#include "remoting/base/constants.h"
#include "remoting/jingle_glue/iq_request.h"
#include "remoting/protocol/content_description.h"
#include "remoting/protocol/jingle_messages.h"
#include "remoting/protocol/pepper_session_manager.h"
#include "remoting/protocol/pepper_stream_channel.h"
#include "third_party/libjingle/source/talk/p2p/base/candidate.h"
#include "third_party/libjingle/source/talk/xmllite/xmlelement.h"
using buzz::XmlElement;
namespace remoting {
namespace protocol {
namespace {
// Delay after candidate creation before sending transport-info
// message. This is neccessary to be able to pack multiple candidates
// into one transport-info messages. The value needs to be greater
// than zero because ports are opened asynchronously in the browser
// process.
const int kTransportInfoSendDelayMs = 2;
} // namespace
PepperSession::PepperSession(PepperSessionManager* session_manager)
: session_manager_(session_manager),
state_(INITIALIZING),
error_(ERROR_NO_ERROR) {
}
PepperSession::~PepperSession() {
control_channel_socket_.reset();
event_channel_socket_.reset();
STLDeleteContainerPairSecondPointers(channels_.begin(), channels_.end());
session_manager_->SessionDestroyed(this);
}
PepperSession::Error PepperSession::error() {
DCHECK(CalledOnValidThread());
return error_;
}
void PepperSession::SetStateChangeCallback(StateChangeCallback* callback) {
DCHECK(CalledOnValidThread());
state_change_callback_.reset(callback);
}
void PepperSession::StartConnection(
const std::string& peer_jid,
const std::string& peer_public_key,
const std::string& client_token,
CandidateSessionConfig* config,
Session::StateChangeCallback* state_change_callback) {
DCHECK(CalledOnValidThread());
peer_jid_ = peer_jid;
peer_public_key_ = peer_public_key;
initiator_token_ = client_token;
candidate_config_.reset(config);
state_change_callback_.reset(state_change_callback);
// Generate random session ID. There are usually not more than 1
// concurrent session per host, so a random 64-bit integer provides
// enough entropy. In the worst case connection will fail when two
// clients generate the same session ID concurrently.
session_id_ = base::Int64ToString(base::RandGenerator(kint64max));
// Send session-initiate message.
JingleMessage message(peer_jid_, JingleMessage::SESSION_INITIATE,
session_id_);
message.from = session_manager_->local_jid_;
message.description.reset(
new ContentDescription(candidate_config_->Clone(), initiator_token_, ""));
initiate_request_.reset(session_manager_->CreateIqRequest());
initiate_request_->set_callback(base::Bind(
&PepperSession::OnSessionInitiateResponse, base::Unretained(this)));
initiate_request_->SendIq(message.ToXml());
SetState(CONNECTING);
}
void PepperSession::OnSessionInitiateResponse(
const buzz::XmlElement* response) {
const std::string& type = response->Attr(buzz::QName("", "type"));
if (type != "result") {
LOG(ERROR) << "Received error in response to session-initiate message: \""
<< response->Str()
<< "\" Terminating the session.";
// TODO(sergeyu): There may be different reasons for error
// here. Parse the response stanza to find failure reason.
OnError(ERROR_PEER_IS_OFFLINE);
}
}
void PepperSession::OnError(Error error) {
error_ = error;
CloseInternal(true);
}
void PepperSession::CreateStreamChannel(
const std::string& name,
const StreamChannelCallback& callback) {
DCHECK(!channels_[name]);
PepperStreamChannel* channel = new PepperStreamChannel(this, name, callback);
channels_[name] = channel;
channel->Connect(session_manager_->pp_instance_,
session_manager_->transport_config_, remote_cert_);
}
void PepperSession::CreateDatagramChannel(
const std::string& name,
const DatagramChannelCallback& callback) {
// TODO(sergeyu): Implement datagram channel support.
NOTREACHED();
}
net::Socket* PepperSession::control_channel() {
DCHECK(CalledOnValidThread());
return control_channel_socket_.get();
}
net::Socket* PepperSession::event_channel() {
DCHECK(CalledOnValidThread());
return event_channel_socket_.get();
}
const std::string& PepperSession::jid() {
DCHECK(CalledOnValidThread());
return peer_jid_;
}
const CandidateSessionConfig* PepperSession::candidate_config() {
DCHECK(CalledOnValidThread());
return candidate_config_.get();
}
const SessionConfig& PepperSession::config() {
DCHECK(CalledOnValidThread());
return config_;
}
void PepperSession::set_config(const SessionConfig& config) {
DCHECK(CalledOnValidThread());
// set_config() should never be called on the client.
NOTREACHED();
}
const std::string& PepperSession::initiator_token() {
DCHECK(CalledOnValidThread());
return initiator_token_;
}
void PepperSession::set_initiator_token(const std::string& initiator_token) {
DCHECK(CalledOnValidThread());
initiator_token_ = initiator_token;
}
const std::string& PepperSession::receiver_token() {
DCHECK(CalledOnValidThread());
return receiver_token_;
}
void PepperSession::set_receiver_token(const std::string& receiver_token) {
DCHECK(CalledOnValidThread());
// set_receiver_token() should not be called on the client side.
NOTREACHED();
}
void PepperSession::set_shared_secret(const std::string& secret) {
DCHECK(CalledOnValidThread());
shared_secret_ = secret;
}
const std::string& PepperSession::shared_secret() {
DCHECK(CalledOnValidThread());
return shared_secret_;
}
void PepperSession::Close() {
DCHECK(CalledOnValidThread());
if (state_ == CONNECTING || state_ == CONNECTED ||
state_ == CONNECTED_CHANNELS) {
// Send session-terminate message.
JingleMessage message(peer_jid_, JingleMessage::SESSION_TERMINATE,
session_id_);
scoped_ptr<IqRequest> terminate_request(
session_manager_->CreateIqRequest());
terminate_request->SendIq(message.ToXml());
}
CloseInternal(false);
}
void PepperSession::OnIncomingMessage(const JingleMessage& message,
JingleMessageReply* reply) {
DCHECK(CalledOnValidThread());
if (message.from != peer_jid_) {
// Ignore messages received from a different Jid.
*reply = JingleMessageReply(JingleMessageReply::INVALID_SID);
return;
}
switch (message.action) {
case JingleMessage::SESSION_ACCEPT:
OnAccept(message, reply);
break;
case JingleMessage::TRANSPORT_INFO:
ProcessTransportInfo(message);
break;
case JingleMessage::SESSION_REJECT:
OnReject(message, reply);
break;
case JingleMessage::SESSION_TERMINATE:
OnTerminate(message, reply);
break;
default:
*reply = JingleMessageReply(JingleMessageReply::UNEXPECTED_REQUEST);
}
}
void PepperSession::OnAccept(const JingleMessage& message,
JingleMessageReply* reply) {
if (state_ != CONNECTING) {
*reply = JingleMessageReply(JingleMessageReply::UNEXPECTED_REQUEST);
return;
}
if (!InitializeConfigFromDescription(message.description.get())) {
OnError(ERROR_INCOMPATIBLE_PROTOCOL);
return;
}
CreateChannels();
SetState(CONNECTED);
// In case there is transport information in the accept message.
ProcessTransportInfo(message);
}
void PepperSession::ProcessTransportInfo(const JingleMessage& message) {
for (std::list<cricket::Candidate>::const_iterator it =
message.candidates.begin();
it != message.candidates.end(); ++it) {
ChannelsMap::iterator channel = channels_.find(it->name());
if (channel == channels_.end()) {
LOG(WARNING) << "Received candidate for unknown channel " << it->name();
continue;
}
channel->second->AddRemoveCandidate(*it);
}
}
void PepperSession::OnReject(const JingleMessage& message,
JingleMessageReply* reply) {
if (state_ != CONNECTING) {
*reply = JingleMessageReply(JingleMessageReply::UNEXPECTED_REQUEST);
return;
}
// TODO(sergeyu): Parse exact rejection reason from reply and pass it
// to OnError().
OnError(ERROR_SESSION_REJECTED);
}
void PepperSession::OnTerminate(const JingleMessage& message,
JingleMessageReply* reply) {
if (state_ == CONNECTING) {
// If we are not connected yet, then interpret terminate message
// as rejection.
OnError(ERROR_SESSION_REJECTED);
return;
}
CloseInternal(false);
}
bool PepperSession::InitializeConfigFromDescription(
const ContentDescription* description) {
DCHECK(description);
remote_cert_ = description->certificate();
if (remote_cert_.empty()) {
LOG(ERROR) << "session-accept does not specify certificate";
return false;
}
if (!description->config()->GetFinalConfig(&config_)) {
LOG(ERROR) << "session-accept does not specify configuration";
return false;
}
if (!candidate_config()->IsSupported(config_)) {
LOG(ERROR) << "session-accept specifies an invalid configuration";
return false;
}
return true;
}
void PepperSession::AddLocalCandidate(const cricket::Candidate& candidate) {
pending_candidates_.push_back(candidate);
if (!transport_infos_timer_.IsRunning()) {
// Delay sending the new candidates in case we get more candidates
// that we can send in one message.
transport_infos_timer_.Start(
FROM_HERE, base::TimeDelta::FromMilliseconds(kTransportInfoSendDelayMs),
this, &PepperSession::SendTransportInfo);
}
}
void PepperSession::OnDeleteChannel(PepperChannel* channel) {
ChannelsMap::iterator it = channels_.find(channel->name());
DCHECK_EQ(it->second, channel);
channels_.erase(it);
}
void PepperSession::SendTransportInfo() {
JingleMessage message(peer_jid_, JingleMessage::TRANSPORT_INFO, session_id_);
message.candidates.swap(pending_candidates_);
scoped_ptr<IqRequest> request(session_manager_->CreateIqRequest());
request->SendIq(message.ToXml());
}
void PepperSession::CreateChannels() {
CreateStreamChannel(
kControlChannelName,
base::Bind(&PepperSession::OnChannelConnected,
base::Unretained(this), &control_channel_socket_));
CreateStreamChannel(
kEventChannelName,
base::Bind(&PepperSession::OnChannelConnected,
base::Unretained(this), &event_channel_socket_));
}
void PepperSession::OnChannelConnected(
scoped_ptr<net::Socket>* socket_container,
net::StreamSocket* socket) {
if (!socket) {
LOG(ERROR) << "Failed to connect control or events channel. "
<< "Terminating connection";
OnError(ERROR_CHANNEL_CONNECTION_FAILURE);
return;
}
socket_container->reset(socket);
if (control_channel_socket_.get() && event_channel_socket_.get())
SetState(CONNECTED_CHANNELS);
}
void PepperSession::CloseInternal(bool failed) {
DCHECK(CalledOnValidThread());
if (state_ != FAILED && state_ != CLOSED) {
control_channel_socket_.reset();
event_channel_socket_.reset();
if (failed)
SetState(FAILED);
else
SetState(CLOSED);
}
}
void PepperSession::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_.get())
state_change_callback_->Run(new_state);
}
}
} // namespace protocol
} // namespace remoting