| Index: remoting/protocol/pepper_session.cc
|
| diff --git a/remoting/protocol/pepper_session.cc b/remoting/protocol/pepper_session.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..03167a901c68fdd201ca88099aa25b5724451777
|
| --- /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
|
|
|