Chromium Code Reviews| 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..073b5bafe6d8feadb5b2875c55cecc5eba8f1c71 |
| --- /dev/null |
| +++ b/remoting/protocol/pepper_session.cc |
| @@ -0,0 +1,384 @@ |
| +// 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 { |
| +const int kTransportInfoSendDelayMs = 2; |
| +} // namespace |
| + |
| +PepperSession::PepperSession(PepperSessionManager* session_manager) |
| + : session_manager_(session_manager), |
| + state_(INITIALIZING), |
| + error_(NO_ERROR) { |
| +} |
| + |
| +PepperSession::~PepperSession() { |
| + control_channel_socket_.reset(); |
| + event_channel_socket_.reset(); |
| + STLDeleteContainerPairSecondPointers(channels_.begin(), channels_.end()); |
| + session_manager_->SessionDestroyed(this); |
| +} |
| + |
| +pp::Instance* PepperSession::pp_instance() { |
| + return session_manager_->pp_instance(); |
| +} |
| + |
| +PepperSession::Error PepperSession::error() { |
| + return error_; |
| +} |
| + |
| +void PepperSession::SetStateChangeCallback(StateChangeCallback* callback) { |
| + state_change_callback_.reset(callback); |
| +} |
| + |
| +void PepperSession::StartConnection( |
| + const std::string& host_jid, |
| + const std::string& host_public_key, |
| + const std::string& client_token, |
| + CandidateSessionConfig* config, |
| + Session::StateChangeCallback* state_change_callback) { |
| + host_jid_ = host_jid; |
| + host_public_key_ = host_public_key; |
| + initiator_token_ = client_token; |
| + candidate_config_.reset(config); |
| + state_change_callback_.reset(state_change_callback); |
| + |
| + sid_ = base::Int64ToString(base::RandGenerator(kint64max)); |
| + |
| + JingleMessage message(host_jid_, JingleMessage::SESSION_INITIATE, sid_); |
| + 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-initate message: \"" |
|
Wez
2011/09/01 01:08:29
nit: initiate
Sergey Ulanov
2011/09/08 00:48:36
Done.
|
| + << response->Str() |
|
Wez
2011/09/01 01:08:29
Are we happy there can be nothing in the response
Sergey Ulanov
2011/09/08 00:48:36
The error message may contain the original session
|
| + << "\" Terminating the session."; |
| + |
| + // TODO(sergeyu): There may be different reasons for error |
| + // here. Parse the response stanza to find falure reason. |
|
Wez
2011/09/01 01:08:29
nit: failure
Sergey Ulanov
2011/09/08 00:48:36
Done.
|
| + OnError(HOST_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_->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 host_jid_; |
| +} |
| + |
| +const CandidateSessionConfig* PepperSession::candidate_config() { |
| + DCHECK(CalledOnValidThread()); |
| + return candidate_config_.get(); |
| +} |
| + |
| +const SessionConfig* PepperSession::config() { |
| + return config_.get(); |
| +} |
| + |
| +void PepperSession::set_config(const SessionConfig* config) { |
| + // 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) { |
| + // 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(host_jid_, JingleMessage::SESSION_TERMINATE, sid_); |
| + 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 != host_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(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): Pass exact rejection reason from reply and pass it |
| + // to OnError(). |
| + OnError(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(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; |
| + } |
| + |
| + config_.reset(description->config()->GetFinalConfig()); |
| + if (!config_.get()) { |
| + LOG(ERROR) << "session-accept does not specify configuration"; |
| + return false; |
| + } |
| + if (!candidate_config()->IsSupported(config_.get())) { |
| + 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( |
| + base::TimeDelta::FromMilliseconds(kTransportInfoSendDelayMs), |
| + this, &PepperSession::SendTransportInfos); |
| + } |
| +} |
| + |
| +void PepperSession::SendTransportInfos() { |
| + JingleMessage message(host_jid_, JingleMessage::TRANSPORT_INFO, sid_); |
| + message.candidates.swap(pending_candidates_); |
| + scoped_ptr<IqRequest> request(session_manager_->CreateIqRequest()); |
| + request->SendIq(message.ToXml()); |
| +} |
| + |
| +void PepperSession::OnDeleteChannel(PepperChannel* channel) { |
| + ChannelsMap::iterator it = channels_.find(channel->name()); |
| + DCHECK_EQ(it->second, channel); |
| + channels_.erase(it); |
| +} |
| + |
| +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(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(); |
| + |
| + // 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 (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 |