diff options
Diffstat (limited to 'remoting/protocol/pepper_session.cc')
-rw-r--r-- | remoting/protocol/pepper_session.cc | 396 |
1 files changed, 396 insertions, 0 deletions
diff --git a/remoting/protocol/pepper_session.cc b/remoting/protocol/pepper_session.cc new file mode 100644 index 0000000..03167a90 --- /dev/null +++ b/remoting/protocol/pepper_session.cc @@ -0,0 +1,396 @@ +// 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 |