// Copyright (c) 2010 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_manager.h"
#include "base/base64.h"
#include "base/message_loop.h"
#include "base/string_number_conversions.h"
#include "remoting/base/constants.h"
#include "remoting/jingle_glue/jingle_thread.h"
#include "remoting/proto/auth.pb.h"
#include "third_party/libjingle/source/talk/p2p/base/constants.h"
#include "third_party/libjingle/source/talk/p2p/base/transport.h"
#include "third_party/libjingle/source/talk/xmllite/xmlelement.h"
using buzz::QName;
using buzz::XmlElement;
namespace remoting {
namespace protocol {
namespace {
const char kDefaultNs[] = "";
const char kChromotingContentName[] = "chromoting";
// Following constants are used to format session description in XML.
const char kDescriptionTag[] = "description";
const char kControlTag[] = "control";
const char kEventTag[] = "event";
const char kVideoTag[] = "video";
const char kResolutionTag[] = "initial-resolution";
const char kAuthenticationTag[] = "authentication";
const char kCertificateTag[] = "certificate";
const char kTransportAttr[] = "transport";
const char kVersionAttr[] = "version";
const char kCodecAttr[] = "codec";
const char kWidthAttr[] = "width";
const char kHeightAttr[] = "height";
const char kStreamTransport[] = "stream";
const char kDatagramTransport[] = "datagram";
const char kSrtpTransport[] = "srtp";
const char kRtpDtlsTransport[] = "rtp-dtls";
const char kVp8Codec[] = "vp8";
const char kZipCodec[] = "zip";
const char* GetTransportName(ChannelConfig::TransportType type) {
switch (type) {
case ChannelConfig::TRANSPORT_STREAM:
return kStreamTransport;
case ChannelConfig::TRANSPORT_DATAGRAM:
return kDatagramTransport;
case ChannelConfig::TRANSPORT_SRTP:
return kSrtpTransport;
case ChannelConfig::TRANSPORT_RTP_DTLS:
return kRtpDtlsTransport;
}
NOTREACHED();
return NULL;
}
const char* GetCodecName(ChannelConfig::Codec type) {
switch (type) {
case ChannelConfig::CODEC_VP8:
return kVp8Codec;
case ChannelConfig::CODEC_ZIP:
return kZipCodec;
default:
break;
}
NOTREACHED();
return NULL;
}
// Format a channel configuration tag for chromotocol session description,
// e.g. for video channel:
//
XmlElement* FormatChannelConfig(const ChannelConfig& config,
const std::string& tag_name) {
XmlElement* result = new XmlElement(
QName(kChromotingXmlNamespace, tag_name));
result->AddAttr(QName(kDefaultNs, kTransportAttr),
GetTransportName(config.transport));
result->AddAttr(QName(kDefaultNs, kVersionAttr),
base::IntToString(config.version));
if (config.codec != ChannelConfig::CODEC_UNDEFINED) {
result->AddAttr(QName(kDefaultNs, kCodecAttr),
GetCodecName(config.codec));
}
return result;
}
bool ParseTransportName(const std::string& value,
ChannelConfig::TransportType* transport) {
if (value == kStreamTransport) {
*transport = ChannelConfig::TRANSPORT_STREAM;
} else if (value == kDatagramTransport) {
*transport = ChannelConfig::TRANSPORT_DATAGRAM;
} else if (value == kSrtpTransport) {
*transport = ChannelConfig::TRANSPORT_SRTP;
} else if (value == kRtpDtlsTransport) {
*transport = ChannelConfig::TRANSPORT_RTP_DTLS;
} else {
return false;
}
return true;
}
bool ParseCodecName(const std::string& value, ChannelConfig::Codec* codec) {
if (value == kVp8Codec) {
*codec = ChannelConfig::CODEC_VP8;
} else if (value == kZipCodec) {
*codec = ChannelConfig::CODEC_ZIP;
} else {
return false;
}
return true;
}
// Returns false if the element is invalid.
bool ParseChannelConfig(const XmlElement* element, bool codec_required,
ChannelConfig* config) {
if (!ParseTransportName(element->Attr(QName(kDefaultNs, kTransportAttr)),
&config->transport) ||
!base::StringToInt(element->Attr(QName(kDefaultNs, kVersionAttr)),
&config->version)) {
return false;
}
if (codec_required) {
if (!ParseCodecName(element->Attr(QName(kDefaultNs, kCodecAttr)),
&config->codec)) {
return false;
}
} else {
config->codec = ChannelConfig::CODEC_UNDEFINED;
}
return true;
}
} // namespace
ContentDescription::ContentDescription(
const CandidateSessionConfig* candidate_config,
const std::string& auth_token,
scoped_refptr certificate)
: candidate_config_(candidate_config),
auth_token_(auth_token),
certificate_(certificate) {
}
ContentDescription::~ContentDescription() { }
JingleSessionManager::JingleSessionManager(
JingleThread* jingle_thread)
: jingle_thread_(jingle_thread),
cricket_session_manager_(NULL),
allow_local_ips_(false),
closed_(false) {
DCHECK(jingle_thread_);
}
void JingleSessionManager::Init(
const std::string& local_jid,
cricket::SessionManager* cricket_session_manager,
IncomingSessionCallback* incoming_session_callback) {
if (MessageLoop::current() != message_loop()) {
message_loop()->PostTask(
FROM_HERE, NewRunnableMethod(
this, &JingleSessionManager::Init,
local_jid, cricket_session_manager, incoming_session_callback));
return;
}
DCHECK(cricket_session_manager);
DCHECK(incoming_session_callback);
local_jid_ = local_jid;
incoming_session_callback_.reset(incoming_session_callback);
cricket_session_manager_ = cricket_session_manager;
cricket_session_manager_->AddClient(kChromotingXmlNamespace, this);
}
JingleSessionManager::~JingleSessionManager() {
DCHECK(closed_);
}
void JingleSessionManager::Close(Task* closed_task) {
if (MessageLoop::current() != message_loop()) {
message_loop()->PostTask(
FROM_HERE, NewRunnableMethod(this, &JingleSessionManager::Close,
closed_task));
return;
}
if (!closed_) {
// Close all connections.
cricket_session_manager_->RemoveClient(kChromotingXmlNamespace);
while (!sessions_.empty()) {
cricket::Session* session = sessions_.front()->ReleaseSession();
cricket_session_manager_->DestroySession(session);
sessions_.pop_front();
}
closed_ = true;
}
closed_task->Run();
delete closed_task;
}
void JingleSessionManager::set_allow_local_ips(bool allow_local_ips) {
allow_local_ips_ = allow_local_ips;
}
scoped_refptr JingleSessionManager::Connect(
const std::string& host_jid,
const std::string& receiver_token,
CandidateSessionConfig* candidate_config,
protocol::Session::StateChangeCallback* state_change_callback) {
// Can be called from any thread.
scoped_refptr jingle_session(
JingleSession::CreateClientSession(this));
jingle_session->set_candidate_config(candidate_config);
jingle_session->set_receiver_token(receiver_token);
message_loop()->PostTask(
FROM_HERE, NewRunnableMethod(this, &JingleSessionManager::DoConnect,
jingle_session, host_jid, receiver_token,
state_change_callback));
return jingle_session;
}
void JingleSessionManager::DoConnect(
scoped_refptr jingle_session,
const std::string& host_jid,
const std::string& receiver_token,
protocol::Session::StateChangeCallback* state_change_callback) {
DCHECK_EQ(message_loop(), MessageLoop::current());
cricket::Session* cricket_session = cricket_session_manager_->CreateSession(
local_jid_, kChromotingXmlNamespace);
// Initialize connection object before we send initiate stanza.
jingle_session->SetStateChangeCallback(state_change_callback);
jingle_session->Init(cricket_session);
sessions_.push_back(jingle_session);
cricket_session->Initiate(
host_jid,
CreateSessionDescription(jingle_session->candidate_config()->Clone(),
receiver_token, NULL));
}
JingleThread* JingleSessionManager::jingle_thread() {
return jingle_thread_;
}
MessageLoop* JingleSessionManager::message_loop() {
return jingle_thread_->message_loop();
}
void JingleSessionManager::OnSessionCreate(
cricket::Session* cricket_session, bool incoming) {
DCHECK_EQ(message_loop(), MessageLoop::current());
// Allow local connections if neccessary.
cricket_session->set_allow_local_ips(allow_local_ips_);
// If this is an outcoming session the session object is already created.
if (incoming) {
// Generate private key and certificate.
// TODO(hclam): Instead of generating we should restore them from the disk.
if (!certificate_) {
private_key_.reset(base::RSAPrivateKey::Create(1024));
certificate_ = net::X509Certificate::CreateSelfSigned(
private_key_.get(), "CN=chromoting", 1,
base::TimeDelta::FromDays(1));
CHECK(certificate_);
}
JingleSession* jingle_session =
JingleSession::CreateServerSession(this, certificate_,
private_key_.release());
certificate_ = NULL;
sessions_.push_back(make_scoped_refptr(jingle_session));
jingle_session->Init(cricket_session);
}
}
void JingleSessionManager::OnSessionDestroy(cricket::Session* cricket_session) {
DCHECK_EQ(message_loop(), MessageLoop::current());
std::list >::iterator it;
for (it = sessions_.begin(); it != sessions_.end(); ++it) {
if ((*it)->HasSession(cricket_session)) {
(*it)->ReleaseSession();
sessions_.erase(it);
return;
}
}
}
void JingleSessionManager::AcceptConnection(
JingleSession* jingle_session,
cricket::Session* cricket_session) {
DCHECK_EQ(message_loop(), MessageLoop::current());
// Reject connection if we are closed.
if (closed_) {
cricket_session->Reject(cricket::STR_TERMINATE_DECLINE);
return;
}
const cricket::SessionDescription* session_description =
cricket_session->remote_description();
const cricket::ContentInfo* content =
session_description->FirstContentByType(kChromotingXmlNamespace);
CHECK(content);
const ContentDescription* content_description =
static_cast(content->description);
jingle_session->set_candidate_config(content_description->config()->Clone());
// Always reject connection if there is no callback.
IncomingSessionResponse response = protocol::SessionManager::DECLINE;
// Use the callback to generate a response.
if (incoming_session_callback_.get())
incoming_session_callback_->Run(jingle_session, &response);
switch (response) {
case protocol::SessionManager::ACCEPT: {
// Connection must be configured by the callback.
DCHECK(jingle_session->config());
CandidateSessionConfig* candidate_config =
CandidateSessionConfig::CreateFrom(jingle_session->config());
cricket_session->Accept(
CreateSessionDescription(candidate_config,
jingle_session->initiator_token(),
jingle_session->server_certificate()));
break;
}
case protocol::SessionManager::INCOMPATIBLE: {
cricket_session->Reject(cricket::STR_TERMINATE_INCOMPATIBLE_PARAMETERS);
break;
}
case protocol::SessionManager::DECLINE: {
cricket_session->Reject(cricket::STR_TERMINATE_DECLINE);
break;
}
default: {
NOTREACHED();
}
}
}
// Parse content description generated by WriteContent().
bool JingleSessionManager::ParseContent(
cricket::SignalingProtocol protocol,
const XmlElement* element,
const cricket::ContentDescription** content,
cricket::ParseError* error) {
if (element->Name() == QName(kChromotingXmlNamespace, kDescriptionTag)) {
scoped_ptr config(
CandidateSessionConfig::CreateEmpty());
const XmlElement* child = NULL;
// tags.
QName control_tag(kChromotingXmlNamespace, kControlTag);
child = element->FirstNamed(control_tag);
while (child) {
ChannelConfig channel_config;
if (!ParseChannelConfig(child, false, &channel_config))
return false;
config->mutable_control_configs()->push_back(channel_config);
child = child->NextNamed(control_tag);
}
// tags.
QName event_tag(kChromotingXmlNamespace, kEventTag);
child = element->FirstNamed(event_tag);
while (child) {
ChannelConfig channel_config;
if (!ParseChannelConfig(child, false, &channel_config))
return false;
config->mutable_event_configs()->push_back(channel_config);
child = child->NextNamed(event_tag);
}
//