<commit_before>// 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_sender.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_(OK) {
}

PepperSession::~PepperSession() {
  control_channel_socket_.reset();
  event_channel_socket_.reset();
  STLDeleteContainerPairSecondPointers(channels_.begin(), channels_.end());
  session_manager_->SessionDestroyed(this);
}

void PepperSession::SetStateChangeCallback(
    const StateChangeCallback& callback) {
  DCHECK(CalledOnValidThread());
  state_change_callback_ = callback;
}

Session::Error PepperSession::error() {
  DCHECK(CalledOnValidThread());
  return error_;
}

void PepperSession::StartConnection(
    const std::string& peer_jid,
    const std::string& peer_public_key,
    const std::string& client_token,
    CandidateSessionConfig* config,
    const 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_ = 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_->iq_sender()->SendIq(
      message.ToXml(),
      base::Bind(&PepperSession::OnSessionInitiateResponse,
                 base::Unretained(this))));

  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(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_->iq_sender()->SendIq(
            message.ToXml(), IqSender::ReplyCallback()));
  }

  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_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::OnTerminate(const JingleMessage& message,
                                JingleMessageReply* reply) {
  if (state_ == CONNECTING) {
    switch (message.reason) {
      case JingleMessage::DECLINE:
        OnError(SESSION_REJECTED);
        break;

      case JingleMessage::INCOMPATIBLE_PARAMETERS:
        OnError(INCOMPATIBLE_PROTOCOL);
        break;

      default:
        LOG(WARNING) << "Received session-terminate message "
            "with an unexpected reason.";
        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;
  }

  if (!description->config()->GetFinalConfig(&config_)) {
    LOG(ERROR) << "session-accept does not specify configuration";<commit_msg>Report correct error when connection cannot be established.<commit_after>// 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_sender.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_(OK) {
}

PepperSession::~PepperSession() {
  control_channel_socket_.reset();
  event_channel_socket_.reset();
  STLDeleteContainerPairSecondPointers(channels_.begin(), channels_.end());
  session_manager_->SessionDestroyed(this);
}

void PepperSession::SetStateChangeCallback(
    const StateChangeCallback& callback) {
  DCHECK(CalledOnValidThread());
  state_change_callback_ = callback;
}

Session::Error PepperSession::error() {
  DCHECK(CalledOnValidThread());
  return error_;
}

void PepperSession::StartConnection(
    const std::string& peer_jid,
    const std::string& peer_public_key,
    const std::string& client_token,
    CandidateSessionConfig* config,
    const 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_ = 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_->iq_sender()->SendIq(
      message.ToXml(),
      base::Bind(&PepperSession::OnSessionInitiateResponse,
                 base::Unretained(this))));

  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(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_->iq_sender()->SendIq(
            message.ToXml(), IqSender::ReplyCallback()));
  }

  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_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::OnTerminate(const JingleMessage& message,
                                JingleMessageReply* reply) {
  if (state_ == CONNECTING) {
    switch (message.reason) {
      case JingleMessage::DECLINE:
        OnError(SESSION_REJECTED);
        break;

      case JingleMessage::INCOMPATIBLE_PARAMETERS:
        OnError(INCOMPATIBLE_PROTOCOL);
        break;

      default:
        LOG(WARNING) << "Received session-terminate message "
            "with an unexpected reason.";
        OnError(SESSION_REJECTED);
    }
    return;
  }

  // TODO(sergeyu): We should return CHANNEL_CONNECTION_ERROR only in
  // case when |message.reason| is set GENERAL_ERROR, but some legacy
  // hosts may sent terminate messages with reason set to SUCCESS.
  if (state_ == CONNECTED) {
    // Session was connected, but we failed to connect channels.
    OnError(CHANNEL_CONNECTION_ERROR);
    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";
{'fraction_non_alphanumeric': 0.11135053601249797, 'fraction_numerical': 0.001400635673113182, 'mean_word_length': 4.343696027633851, 'pattern_counts': {'":': 0, '<': 39, '<?xml version=': 0, '>': 45, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 4}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1063020', 'n_tokens_mistral': 5495, 'n_tokens_neox': 5420, 'n_words': 1169}