summaryrefslogtreecommitdiffstats
path: root/remoting/protocol/pepper_session.cc
diff options
context:
space:
mode:
Diffstat (limited to 'remoting/protocol/pepper_session.cc')
-rw-r--r--remoting/protocol/pepper_session.cc396
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