/*
 * libjingle
 * Copyright 2004 Google Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright notice,
 *     this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *  3. The name of the author may not be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <string.h>

#include <deque>
#include <map>
#include <sstream>

#include "talk/p2p/base/basicpacketsocketfactory.h"
#include "talk/p2p/base/constants.h"
#include "talk/p2p/base/p2ptransport.h"
#include "talk/p2p/base/parsing.h"
#include "talk/p2p/base/portallocator.h"
#include "talk/p2p/base/relayport.h"
#include "talk/p2p/base/relayserver.h"
#include "talk/p2p/base/session.h"
#include "talk/p2p/base/sessionclient.h"
#include "talk/p2p/base/sessionmanager.h"
#include "talk/p2p/base/stunport.h"
#include "talk/p2p/base/stunserver.h"
#include "talk/p2p/base/transportchannel.h"
#include "talk/p2p/base/transportchannelproxy.h"
#include "talk/p2p/base/udpport.h"
#include "talk/xmpp/constants.h"
#include "webrtc/base/base64.h"
#include "webrtc/base/common.h"
#include "webrtc/base/gunit.h"
#include "webrtc/base/helpers.h"
#include "webrtc/base/logging.h"
#include "webrtc/base/natserver.h"
#include "webrtc/base/natsocketfactory.h"
#include "webrtc/base/stringencode.h"

using cricket::SignalingProtocol;
using cricket::PROTOCOL_HYBRID;
using cricket::PROTOCOL_JINGLE;
using cricket::PROTOCOL_GINGLE;

static const std::string kInitiator = "init@init.com";
static const std::string kResponder = "resp@resp.com";
// Expected from test random number generator.
static const std::string kSessionId = "9254631414740579489";
// TODO: When we need to test more than one transport type,
// allow this to be injected like the content types are.
static const std::string kTransportType = "http://www.google.com/transport/p2p";

// Controls how long we wait for a session to send messages that we
// expect, in milliseconds.  We put it high to avoid flaky tests.
static const int kEventTimeout = 5000;

static const int kNumPorts = 2;
static const int kPort0 = 28653;
static const int kPortStep = 5;

int GetPort(int port_index) {
  return kPort0 + (port_index * kPortStep);
}

std::string GetPortString(int port_index) {
  return rtc::ToString(GetPort(port_index));
}

// Only works for port_index < 10, which is fine for our purposes.
std::string GetUsername(int port_index) {
  return "username" + std::string(8, rtc::ToString(port_index)[0]);
}

// Only works for port_index < 10, which is fine for our purposes.
std::string GetPassword(int port_index) {
  return "password" + std::string(8, rtc::ToString(port_index)[0]);
}

std::string IqAck(const std::string& id,
                  const std::string& from,
                  const std::string& to) {
  return "<cli:iq"
      " to=\"" + to + "\""
      " id=\"" + id + "\""
      " type=\"result\""
      " from=\"" + from + "\""
      " xmlns:cli=\"jabber:client\""
      "/>";
}

std::string IqSet(const std::string& id,
                  const std::string& from,
                  const std::string& to,
                  const std::string& content) {
  return "<cli:iq"
      " to=\"" + to + "\""
      " type=\"set\""
      " from=\"" + from + "\""
      " id=\"" + id + "\""
      " xmlns:cli=\"jabber:client\""
      ">"
      + content +
      "</cli:iq>";
}

std::string IqError(const std::string& id,
                    const std::string& from,
                    const std::string& to,
                    const std::string& content) {
  return "<cli:error"
      " to=\"" + to + "\""
      " type=\"error\""
      " from=\"" + from + "\""
      " id=\"" + id + "\""
      " xmlns:cli=\"jabber:client\""
      ">"
      + content +
      "</cli:error>";
}

std::string GingleSessionXml(const std::string& type,
                             const std::string& content) {
  return "<session"
      " xmlns=\"http://www.google.com/session\""
      " type=\"" + type + "\""
      " id=\"" + kSessionId + "\""
      " initiator=\"" + kInitiator + "\""
      ">"
      + content +
      "</session>";
}

std::string GingleDescriptionXml(const std::string& content_type) {
  return "<description"
      " xmlns=\"" + content_type + "\""
      "/>";
}

std::string P2pCandidateXml(const std::string& name, int port_index) {
  // Port will update the rtcp username by +1 on the last character. So we need
  // to compensate here. See Port::username_fragment() for detail.
  std::string username = GetUsername(port_index);
  // TODO: Use the component id instead of the channel name to
  // determinte if we need to covert the username here.
  if (name == "rtcp" || name == "video_rtcp" || name == "chanb") {
    char next_ch = username[username.size() - 1];
    ASSERT(username.size() > 0);
    rtc::Base64::GetNextBase64Char(next_ch, &next_ch);
    username[username.size() - 1] = next_ch;
  }
  return "<candidate"
      " name=\"" + name + "\""
      " address=\"127.0.0.1\""
      " port=\"" + GetPortString(port_index) + "\""
      " preference=\"0.99\""
      " username=\"" + username + "\""
      " protocol=\"udp\""
      " generation=\"0\""
      " password=\"" + GetPassword(port_index) + "\""
      " type=\"local\""
      " network=\"network\""
      "/>";
}

std::string JingleActionXml(const std::string& action,
                            const std::string& content) {
  return "<jingle"
      " xmlns=\"urn:xmpp:jingle:1\""
      " action=\"" + action + "\""
      " sid=\"" + kSessionId + "\""
      ">"
      + content +
      "</jingle>";
}

std::string JingleInitiateActionXml(const std::string& content) {
  return "<jingle"
      " xmlns=\"urn:xmpp:jingle:1\""
      " action=\"session-initiate\""
      " sid=\"" + kSessionId + "\""
      " initiator=\"" + kInitiator + "\""
      ">"
      + content +
      "</jingle>";
}

std::string JingleGroupInfoXml(const std::string& content_name_a,
                               const std::string& content_name_b) {
  std::string group_info = "<jin:group"
      " type=\"BUNDLE\""
      " xmlns:jin=\"google:jingle\""
      ">";
  if (!content_name_a.empty())
    group_info += "<content name=\"" + content_name_a + "\""
    "/>";
  if (!content_name_b.empty())
    group_info += "<content name=\"" + content_name_b + "\""
    "/>";
  group_info += "</jin:group>";
  return group_info;
}


std::string JingleEmptyContentXml(const std::string& content_name,
                                  const std::string& content_type,
                                  const std::string& transport_type) {
  return "<content"
      " name=\"" + content_name + "\""
      " creator=\"initiator\""
      ">"
      "<description"
      " xmlns=\"" + content_type + "\""
      "/>"
      "<transport"
      " xmlns=\"" + transport_type + "\""
      "/>"
      "</content>";
}

std::string JingleContentXml(const std::string& content_name,
                             const std::string& content_type,
                             const std::string& transport_type,
                             const std::string& transport_main) {
  std::string transport = transport_type.empty() ? "" :
      "<transport"
      " xmlns=\"" + transport_type + "\""
      ">"
      + transport_main +
      "</transport>";

  return"<content"
      " name=\"" + content_name + "\""
      " creator=\"initiator\""
      ">"
      "<description"
      " xmlns=\"" + content_type + "\""
      "/>"
      + transport +
      "</content>";
}

std::string JingleTransportContentXml(const std::string& content_name,
                                      const std::string& transport_type,
                                      const std::string& content) {
  return "<content"
      " name=\"" + content_name + "\""
      " creator=\"initiator\""
      ">"
      "<transport"
      " xmlns=\"" + transport_type + "\""
      ">"
      + content +
      "</transport>"
      "</content>";
}

std::string GingleInitiateXml(const std::string& content_type) {
  return GingleSessionXml(
      "initiate",
      GingleDescriptionXml(content_type));
}

std::string JingleInitiateXml(const std::string& content_name_a,
                              const std::string& content_type_a,
                              const std::string& content_name_b,
                              const std::string& content_type_b,
                              bool bundle = false) {
  std::string content_xml;
  if (content_name_b.empty()) {
    content_xml = JingleEmptyContentXml(
        content_name_a, content_type_a, kTransportType);
  } else {
    content_xml = JingleEmptyContentXml(
           content_name_a, content_type_a, kTransportType) +
       JingleEmptyContentXml(
           content_name_b, content_type_b, kTransportType);
    if (bundle) {
      content_xml += JingleGroupInfoXml(content_name_a, content_name_b);
    }
  }
  return JingleInitiateActionXml(content_xml);
}

std::string GingleAcceptXml(const std::string& content_type) {
  return GingleSessionXml(
      "accept",
      GingleDescriptionXml(content_type));
}

std::string JingleAcceptXml(const std::string& content_name_a,
                            const std::string& content_type_a,
                            const std::string& content_name_b,
                            const std::string& content_type_b,
                            bool bundle = false) {
  std::string content_xml;
  if (content_name_b.empty()) {
    content_xml = JingleEmptyContentXml(
        content_name_a, content_type_a, kTransportType);
  } else {
    content_xml = JingleEmptyContentXml(
        content_name_a, content_type_a, kTransportType) +
        JingleEmptyContentXml(
            content_name_b, content_type_b, kTransportType);
  }
  if (bundle) {
    content_xml += JingleGroupInfoXml(content_name_a, content_name_b);
  }

  return JingleActionXml("session-accept", content_xml);
}

std::string Gingle2CandidatesXml(const std::string& channel_name,
                                 int port_index0,
                                 int port_index1) {
  return GingleSessionXml(
      "candidates",
      P2pCandidateXml(channel_name, port_index0) +
      P2pCandidateXml(channel_name, port_index1));
}

std::string Gingle4CandidatesXml(const std::string& channel_name_a,
                                 int port_index0,
                                 int port_index1,
                                 const std::string& channel_name_b,
                                 int port_index2,
                                 int port_index3) {
  return GingleSessionXml(
      "candidates",
      P2pCandidateXml(channel_name_a, port_index0) +
      P2pCandidateXml(channel_name_a, port_index1) +
      P2pCandidateXml(channel_name_b, port_index2) +
      P2pCandidateXml(channel_name_b, port_index3));
}

std::string Jingle2TransportInfoXml(const std::string& content_name,
                                    const std::string& channel_name,
                                    int port_index0,
                                    int port_index1) {
  return JingleActionXml(
      "transport-info",
      JingleTransportContentXml(
          content_name, kTransportType,
          P2pCandidateXml(channel_name, port_index0) +
          P2pCandidateXml(channel_name, port_index1)));
}

std::string Jingle4TransportInfoXml(const std::string& content_name,
                                    const std::string& channel_name_a,
                                    int port_index0,
                                    int port_index1,
                                    const std::string& channel_name_b,
                                    int port_index2,
                                    int port_index3) {
  return JingleActionXml(
      "transport-info",
      JingleTransportContentXml(
          content_name, kTransportType,
          P2pCandidateXml(channel_name_a, port_index0) +
          P2pCandidateXml(channel_name_a, port_index1) +
          P2pCandidateXml(channel_name_b, port_index2) +
          P2pCandidateXml(channel_name_b, port_index3)));
}

std::string JingleDescriptionInfoXml(const std::string& content_name,
                                     const std::string& content_type) {
  return JingleActionXml(
      "description-info",
      JingleContentXml(content_name, content_type, "", ""));
}

std::string GingleRejectXml(const std::string& reason) {
  return GingleSessionXml(
      "reject",
      "<" + reason + "/>");
}

std::string JingleTerminateXml(const std::string& reason) {
    return JingleActionXml(
        "session-terminate",
        "<reason><" + reason + "/></reason>");
}

std::string GingleTerminateXml(const std::string& reason) {
  return GingleSessionXml(
      "terminate",
      "<" + reason + "/>");
}

std::string GingleRedirectXml(const std::string& intitiate,
                              const std::string& target) {
  return intitiate +
    "<error code=\"302\" type=\"modify\">"
    "<redirect xmlns=\"http://www.google.com/session\">"
    "xmpp:" + target +
    "</redirect>"
    "</error>";
}

std::string JingleRedirectXml(const std::string& intitiate,
                              const std::string& target) {
  return intitiate +
    "<error code=\"302\" type=\"modify\">"
    "<redirect xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\">"
    "xmpp:" + target +
    "</redirect>"
    "</error>";
}

std::string InitiateXml(SignalingProtocol protocol,
                        const std::string& gingle_content_type,
                        const std::string& content_name_a,
                        const std::string& content_type_a,
                        const std::string& content_name_b,
                        const std::string& content_type_b,
                        bool bundle = false) {
  switch (protocol) {
    case PROTOCOL_JINGLE:
      return JingleInitiateXml(content_name_a, content_type_a,
                               content_name_b, content_type_b,
                               bundle);
    case PROTOCOL_GINGLE:
      return GingleInitiateXml(gingle_content_type);
    case PROTOCOL_HYBRID:
      return JingleInitiateXml(content_name_a, content_type_a,
                               content_name_b, content_type_b) +
          GingleInitiateXml(gingle_content_type);
  }
  return "";
}

std::string InitiateXml(SignalingProtocol protocol,
                        const std::string& content_name,
                        const std::string& content_type) {
  return InitiateXml(protocol,
                     content_type,
                     content_name, content_type,
                     "", "");
}

std::string AcceptXml(SignalingProtocol protocol,
                      const std::string& gingle_content_type,
                      const std::string& content_name_a,
                      const std::string& content_type_a,
                      const std::string& content_name_b,
                      const std::string& content_type_b,
                      bool bundle = false) {
  switch (protocol) {
    case PROTOCOL_JINGLE:
      return JingleAcceptXml(content_name_a, content_type_a,
                             content_name_b, content_type_b, bundle);
    case PROTOCOL_GINGLE:
      return GingleAcceptXml(gingle_content_type);
    case PROTOCOL_HYBRID:
      return
          JingleAcceptXml(content_name_a, content_type_a,
                          content_name_b, content_type_b) +
          GingleAcceptXml(gingle_content_type);
  }
  return "";
}


std::string AcceptXml(SignalingProtocol protocol,
                      const std::string& content_name,
                      const std::string& content_type,
                      bool bundle = false) {
  return AcceptXml(protocol,
                   content_type,
                   content_name, content_type,
                   "", "");
}

std::string TransportInfo2Xml(SignalingProtocol protocol,
                              const std::string& content_name,
                              const std::string& channel_name,
                              int port_index0,
                              int port_index1) {
  switch (protocol) {
    case PROTOCOL_JINGLE:
      return Jingle2TransportInfoXml(
          content_name,
          channel_name, port_index0, port_index1);
    case PROTOCOL_GINGLE:
      return Gingle2CandidatesXml(
          channel_name, port_index0, port_index1);
    case PROTOCOL_HYBRID:
      return
          Jingle2TransportInfoXml(
              content_name,
              channel_name, port_index0, port_index1) +
          Gingle2CandidatesXml(
              channel_name, port_index0, port_index1);
  }
  return "";
}

std::string TransportInfo4Xml(SignalingProtocol protocol,
                              const std::string& content_name,
                              const std::string& channel_name_a,
                              int port_index0,
                              int port_index1,
                              const std::string& channel_name_b,
                              int port_index2,
                              int port_index3) {
  switch (protocol) {
    case PROTOCOL_JINGLE:
      return Jingle4TransportInfoXml(
          content_name,
          channel_name_a, port_index0, port_index1,
          channel_name_b, port_index2, port_index3);
    case PROTOCOL_GINGLE:
      return Gingle4CandidatesXml(
          channel_name_a, port_index0, port_index1,
          channel_name_b, port_index2, port_index3);
    case PROTOCOL_HYBRID:
      return
          Jingle4TransportInfoXml(
              content_name,
              channel_name_a, port_index0, port_index1,
              channel_name_b, port_index2, port_index3) +
          Gingle4CandidatesXml(
              channel_name_a, port_index0, port_index1,
              channel_name_b, port_index2, port_index3);
  }
  return "";
}

std::string RejectXml(SignalingProtocol protocol,
                      const std::string& reason) {
  switch (protocol) {
    case PROTOCOL_JINGLE:
      return JingleTerminateXml(reason);
    case PROTOCOL_GINGLE:
      return GingleRejectXml(reason);
    case PROTOCOL_HYBRID:
      return JingleTerminateXml(reason) +
          GingleRejectXml(reason);
  }
  return "";
}

std::string TerminateXml(SignalingProtocol protocol,
                         const std::string& reason) {
  switch (protocol) {
    case PROTOCOL_JINGLE:
      return JingleTerminateXml(reason);
    case PROTOCOL_GINGLE:
      return GingleTerminateXml(reason);
    case PROTOCOL_HYBRID:
      return JingleTerminateXml(reason) +
          GingleTerminateXml(reason);
  }
  return "";
}

std::string RedirectXml(SignalingProtocol protocol,
                        const std::string& initiate,
                        const std::string& target) {
  switch (protocol) {
    case PROTOCOL_JINGLE:
      return JingleRedirectXml(initiate, target);
    case PROTOCOL_GINGLE:
      return GingleRedirectXml(initiate, target);
    default:
      break;
  }
  return "";
}

// TODO: Break out and join with fakeportallocator.h
class TestPortAllocatorSession : public cricket::PortAllocatorSession {
 public:
  TestPortAllocatorSession(const std::string& content_name,
                           int component,
                           const std::string& ice_ufrag,
                           const std::string& ice_pwd,
                           const int port_offset)
      : PortAllocatorSession(content_name, component, ice_ufrag, ice_pwd, 0),
        port_offset_(port_offset),
        ports_(kNumPorts),
        address_("127.0.0.1", 0),
        network_("network", "unittest",
                 rtc::IPAddress(INADDR_LOOPBACK), 8),
        socket_factory_(rtc::Thread::Current()),
        running_(false),
        port_(28653) {
    network_.AddIP(address_.ipaddr());
  }

  ~TestPortAllocatorSession() {
    for (size_t i = 0; i < ports_.size(); i++)
      delete ports_[i];
  }

  virtual void StartGettingPorts() {
    for (int i = 0; i < kNumPorts; i++) {
      int index = port_offset_ + i;
      ports_[i] = cricket::UDPPort::Create(
          rtc::Thread::Current(), &socket_factory_,
          &network_, address_.ipaddr(), GetPort(index), GetPort(index),
          GetUsername(index), GetPassword(index));
      AddPort(ports_[i]);
    }
    running_ = true;
  }

  virtual void StopGettingPorts() { running_ = false; }
  virtual bool IsGettingPorts() { return running_; }

  void AddPort(cricket::Port* port) {
    port->set_component(component_);
    port->set_generation(0);
    port->SignalDestroyed.connect(
        this, &TestPortAllocatorSession::OnPortDestroyed);
    port->SignalPortComplete.connect(
        this, &TestPortAllocatorSession::OnPortComplete);
    port->PrepareAddress();
    SignalPortReady(this, port);
  }

  void OnPortDestroyed(cricket::PortInterface* port) {
    for (size_t i = 0; i < ports_.size(); i++) {
      if (ports_[i] == port)
        ports_[i] = NULL;
    }
  }

  void OnPortComplete(cricket::Port* port) {
    SignalCandidatesReady(this, port->Candidates());
  }

 private:
  int port_offset_;
  std::vector<cricket::Port*> ports_;
  rtc::SocketAddress address_;
  rtc::Network network_;
  rtc::BasicPacketSocketFactory socket_factory_;
  bool running_;
  int port_;
};

class TestPortAllocator : public cricket::PortAllocator {
 public:
  TestPortAllocator() : port_offset_(0) {}

  virtual cricket::PortAllocatorSession*
  CreateSessionInternal(
                const std::string& content_name,
                int component,
                const std::string& ice_ufrag,
                const std::string& ice_pwd) {
    port_offset_ += 2;
    return new TestPortAllocatorSession(content_name, component,
                                        ice_ufrag, ice_pwd, port_offset_ - 2);
  }

  int port_offset_;
};

class TestContentDescription : public cricket::ContentDescription {
 public:
  explicit TestContentDescription(const std::string& gingle_content_type,
                                  const std::string& content_type)
      : gingle_content_type(gingle_content_type),
        content_type(content_type) {
  }
  virtual ContentDescription* Copy() const {
    return new TestContentDescription(*this);
  }

  std::string gingle_content_type;
  std::string content_type;
};

cricket::SessionDescription* NewTestSessionDescription(
    const std::string gingle_content_type,
    const std::string& content_name_a, const std::string& content_type_a,
    const std::string& content_name_b, const std::string& content_type_b) {

  cricket::SessionDescription* offer = new cricket::SessionDescription();
  offer->AddContent(content_name_a, content_type_a,
                    new TestContentDescription(gingle_content_type,
                                               content_type_a));
  cricket::TransportDescription desc(cricket::NS_GINGLE_P2P,
                                     std::string(), std::string());
  offer->AddTransportInfo(cricket::TransportInfo(content_name_a, desc));

  if (content_name_a != content_name_b) {
    offer->AddContent(content_name_b, content_type_b,
                      new TestContentDescription(gingle_content_type,
                                                 content_type_b));
    offer->AddTransportInfo(cricket::TransportInfo(content_name_b, desc));
  }
  return offer;
}

cricket::SessionDescription* NewTestSessionDescription(
    const std::string& content_name, const std::string& content_type) {

  cricket::SessionDescription* offer = new cricket::SessionDescription();
  offer->AddContent(content_name, content_type,
                    new TestContentDescription(content_type,
                                               content_type));
  offer->AddTransportInfo(cricket::TransportInfo
                          (content_name, cricket::TransportDescription(
                          cricket::NS_GINGLE_P2P,
                          std::string(), std::string())));
  return offer;
}

struct TestSessionClient: public cricket::SessionClient,
                          public sigslot::has_slots<> {
 public:
  TestSessionClient() {
  }

  ~TestSessionClient() {
  }

  virtual bool ParseContent(SignalingProtocol protocol,
                            const buzz::XmlElement* elem,
                            cricket::ContentDescription** content,
                            cricket::ParseError* error) {
    std::string content_type;
    std::string gingle_content_type;
    if (protocol == PROTOCOL_GINGLE) {
      gingle_content_type = elem->Name().Namespace();
    } else {
      content_type = elem->Name().Namespace();
    }

    *content = new TestContentDescription(gingle_content_type, content_type);
    return true;
  }

  virtual bool WriteContent(SignalingProtocol protocol,
                            const cricket::ContentDescription* untyped_content,
                            buzz::XmlElement** elem,
                            cricket::WriteError* error) {
    const TestContentDescription* content =
        static_cast<const TestContentDescription*>(untyped_content);
    std::string content_type = (protocol == PROTOCOL_GINGLE ?
                                content->gingle_content_type :
                                content->content_type);
     *elem = new buzz::XmlElement(
        buzz::QName(content_type, "description"), true);
    return true;
  }

  void OnSessionCreate(cricket::Session* session, bool initiate) {
  }

  void OnSessionDestroy(cricket::Session* session) {
  }
};

struct ChannelHandler : sigslot::has_slots<> {
  explicit ChannelHandler(cricket::TransportChannel* p, const std::string& name)
    : channel(p), last_readable(false), last_writable(false), data_count(0),
      last_size(0), name(name) {
    p->SignalReadableState.connect(this, &ChannelHandler::OnReadableState);
    p->SignalWritableState.connect(this, &ChannelHandler::OnWritableState);
    p->SignalReadPacket.connect(this, &ChannelHandler::OnReadPacket);
  }

  bool writable() const {
    return last_writable && channel->writable();
  }

  bool readable() const {
    return last_readable && channel->readable();
  }

  void OnReadableState(cricket::TransportChannel* p) {
    EXPECT_EQ(channel, p);
    last_readable = channel->readable();
  }

  void OnWritableState(cricket::TransportChannel* p) {
    EXPECT_EQ(channel, p);
    last_writable = channel->writable();
  }

  void OnReadPacket(cricket::TransportChannel* p, const char* buf,
                    size_t size, const rtc::PacketTime& time, int flags) {
    if (memcmp(buf, name.c_str(), name.size()) != 0)
      return;  // drop packet if packet doesn't belong to this channel. This
               // can happen when transport channels are muxed together.
    buf += name.size();  // Remove channel name from the message.
    size -= name.size();  // Decrement size by channel name string size.
    EXPECT_EQ(channel, p);
    EXPECT_LE(size, sizeof(last_data));
    data_count += 1;
    last_size = size;
    memcpy(last_data, buf, size);
  }

  void Send(const char* data, size_t size) {
    rtc::PacketOptions options;
    std::string data_with_id(name);
    data_with_id += data;
    int result = channel->SendPacket(data_with_id.c_str(), data_with_id.size(),
                                     options, 0);
    EXPECT_EQ(static_cast<int>(data_with_id.size()), result);
  }

  cricket::TransportChannel* channel;
  bool last_readable, last_writable;
  int data_count;
  char last_data[4096];
  size_t last_size;
  std::string name;
};

void PrintStanza(const std::string& message,
                 const buzz::XmlElement* stanza) {
  printf("%s: %s\n", message.c_str(), stanza->Str().c_str());
}

class TestClient : public sigslot::has_slots<> {
 public:
  // TODO: Add channel_component_a/b as inputs to the ctor.
  TestClient(cricket::PortAllocator* port_allocator,
             int* next_message_id,
             const std::string& local_name,
             SignalingProtocol start_protocol,
             const std::string& content_type,
             const std::string& content_name_a,
             const std::string& channel_name_a,
             const std::string& content_name_b,
             const std::string& channel_name_b) {
    Construct(port_allocator, next_message_id, local_name, start_protocol,
              content_type, content_name_a, channel_name_a,
              content_name_b, channel_name_b);
  }

  ~TestClient() {
    if (session) {
      session_manager->DestroySession(session);
      EXPECT_EQ(1U, session_destroyed_count);
    }
    delete session_manager;
    delete client;
    for (std::deque<buzz::XmlElement*>::iterator it = sent_stanzas.begin();
         it != sent_stanzas.end(); ++it) {
      delete *it;
    }
  }

  void Construct(cricket::PortAllocator* pa,
                 int* message_id,
                 const std::string& lname,
                 SignalingProtocol protocol,
                 const std::string& cont_type,
                 const std::string& cont_name_a,
                 const std::string& chan_name_a,
                 const std::string& cont_name_b,
                 const std::string& chan_name_b) {
    port_allocator_ = pa;
    next_message_id = message_id;
    local_name = lname;
    start_protocol = protocol;
    content_type = cont_type;
    content_name_a = cont_name_a;
    channel_name_a = chan_name_a;
    content_name_b = cont_name_b;
    channel_name_b = chan_name_b;
    session_created_count = 0;
    session_destroyed_count = 0;
    session_remote_description_update_count = 0;
    new_local_description = false;
    new_remote_description = false;
    last_content_action = cricket::CA_OFFER;
    last_content_source = cricket::CS_LOCAL;
    session = NULL;
    last_session_state = cricket::BaseSession::STATE_INIT;
    blow_up_on_error = true;
    error_count = 0;

    session_manager = new cricket::SessionManager(port_allocator_);
    session_manager->SignalSessionCreate.connect(
        this, &TestClient::OnSessionCreate);
    session_manager->SignalSessionDestroy.connect(
        this, &TestClient::OnSessionDestroy);
    session_manager->SignalOutgoingMessage.connect(
        this, &TestClient::OnOutgoingMessage);

    client = new TestSessionClient();
    session_manager->AddClient(content_type, client);
    EXPECT_EQ(client, session_manager->GetClient(content_type));
  }

  uint32 sent_stanza_count() const {
    return static_cast<uint32>(sent_stanzas.size());
  }

  const buzz::XmlElement* stanza() const {
    return last_expected_sent_stanza.get();
  }

  cricket::BaseSession::State session_state() const {
    EXPECT_EQ(last_session_state, session->state());
    return session->state();
  }

  void SetSessionState(cricket::BaseSession::State state) {
    session->SetState(state);
    EXPECT_EQ_WAIT(last_session_state, session->state(), kEventTimeout);
  }

  void CreateSession() {
    session_manager->CreateSession(local_name, content_type);
  }

  void DeliverStanza(const buzz::XmlElement* stanza) {
    session_manager->OnIncomingMessage(stanza);
  }

  void DeliverStanza(const std::string& str) {
    buzz::XmlElement* stanza = buzz::XmlElement::ForStr(str);
    session_manager->OnIncomingMessage(stanza);
    delete stanza;
  }

  void DeliverAckToLastStanza() {
    const buzz::XmlElement* orig_stanza = stanza();
    const buzz::XmlElement* response_stanza =
        buzz::XmlElement::ForStr(IqAck(orig_stanza->Attr(buzz::QN_IQ), "", ""));
    session_manager->OnIncomingResponse(orig_stanza, response_stanza);
    delete response_stanza;
  }

  void ExpectSentStanza(const std::string& expected) {
    EXPECT_TRUE(!sent_stanzas.empty()) <<
        "Found no stanza when expected " << expected;

    last_expected_sent_stanza.reset(sent_stanzas.front());
    sent_stanzas.pop_front();

    std::string actual = last_expected_sent_stanza->Str();
    EXPECT_EQ(expected, actual);
  }

  void SkipUnsentStanza() {
    GetNextOutgoingMessageID();
  }

  bool HasTransport(const std::string& content_name) const {
    ASSERT(session != NULL);
    const cricket::Transport* transport = session->GetTransport(content_name);
    return transport != NULL && (kTransportType == transport->type());
  }

  bool HasChannel(const std::string& content_name,
                  int component) const {
    ASSERT(session != NULL);
    const cricket::TransportChannel* channel =
        session->GetChannel(content_name, component);
    return channel != NULL && (component == channel->component());
  }

  cricket::TransportChannel* GetChannel(const std::string& content_name,
                                        int component) const {
    ASSERT(session != NULL);
    return session->GetChannel(content_name, component);
  }

  void OnSessionCreate(cricket::Session* created_session, bool initiate) {
    session_created_count += 1;

    session = created_session;
    session->set_current_protocol(start_protocol);
    session->SignalState.connect(this, &TestClient::OnSessionState);
    session->SignalError.connect(this, &TestClient::OnSessionError);
    session->SignalRemoteDescriptionUpdate.connect(
        this, &TestClient::OnSessionRemoteDescriptionUpdate);
    session->SignalNewLocalDescription.connect(
        this, &TestClient::OnNewLocalDescription);
    session->SignalNewRemoteDescription.connect(
        this, &TestClient::OnNewRemoteDescription);

    CreateChannels();
  }

  void OnSessionDestroy(cricket::Session *session) {
    session_destroyed_count += 1;
  }

  void OnSessionState(cricket::BaseSession* session,
                      cricket::BaseSession::State state) {
    // EXPECT_EQ does not allow use of this, hence the tmp variable.
    cricket::BaseSession* tmp = this->session;
    EXPECT_EQ(tmp, session);
    last_session_state = state;
  }

  void OnSessionError(cricket::BaseSession* session,
                      cricket::BaseSession::Error error) {
    // EXPECT_EQ does not allow use of this, hence the tmp variable.
    cricket::BaseSession* tmp = this->session;
    EXPECT_EQ(tmp, session);
    if (blow_up_on_error) {
      EXPECT_TRUE(false);
    } else {
      error_count++;
    }
  }

  void OnSessionRemoteDescriptionUpdate(cricket::BaseSession* session,
      const cricket::ContentInfos& contents) {
    session_remote_description_update_count++;
  }

  void OnNewLocalDescription(cricket::BaseSession* session,
                             cricket::ContentAction action) {
    new_local_description = true;
    last_content_action = action;
    last_content_source = cricket::CS_LOCAL;
  }

  void OnNewRemoteDescription(cricket::BaseSession* session,
                              cricket::ContentAction action) {
    new_remote_description = true;
    last_content_action = action;
    last_content_source = cricket::CS_REMOTE;
  }

  void PrepareCandidates() {
    session_manager->OnSignalingReady();
  }

  void OnOutgoingMessage(cricket::SessionManager* manager,
                         const buzz::XmlElement* stanza) {
    buzz::XmlElement* elem = new buzz::XmlElement(*stanza);
    EXPECT_TRUE(elem->Name() == buzz::QN_IQ);
    EXPECT_TRUE(elem->HasAttr(buzz::QN_TO));
    EXPECT_FALSE(elem->HasAttr(buzz::QN_FROM));
    EXPECT_TRUE(elem->HasAttr(buzz::QN_TYPE));
    EXPECT_TRUE((elem->Attr(buzz::QN_TYPE) == "set") ||
                (elem->Attr(buzz::QN_TYPE) == "result") ||
                (elem->Attr(buzz::QN_TYPE) == "error"));

    elem->SetAttr(buzz::QN_FROM, local_name);
    if (elem->Attr(buzz::QN_TYPE) == "set") {
      EXPECT_FALSE(elem->HasAttr(buzz::QN_ID));
      elem->SetAttr(buzz::QN_ID, GetNextOutgoingMessageID());
    }

    // Uncommenting this is useful for debugging.
    // PrintStanza("OutgoingMessage", elem);
    sent_stanzas.push_back(elem);
  }

  std::string GetNextOutgoingMessageID() {
    int message_id = (*next_message_id)++;
    std::ostringstream ost;
    ost << message_id;
    return ost.str();
  }

  void CreateChannels() {
    ASSERT(session != NULL);
    // We either have a single content with multiple components (RTP/RTCP), or
    // multiple contents with single components, but not both.
    int component_a = 1;
    int component_b = (content_name_a == content_name_b) ? 2 : 1;
    chan_a.reset(new ChannelHandler(
        session->CreateChannel(content_name_a, channel_name_a, component_a),
        channel_name_a));
    chan_b.reset(new ChannelHandler(
        session->CreateChannel(content_name_b, channel_name_b, component_b),
        channel_name_b));
  }

  int* next_message_id;
  std::string local_name;
  SignalingProtocol start_protocol;
  std::string content_type;
  std::string content_name_a;
  std::string channel_name_a;
  std::string content_name_b;
  std::string channel_name_b;

  uint32 session_created_count;
  uint32 session_destroyed_count;
  uint32 session_remote_description_update_count;
  bool new_local_description;
  bool new_remote_description;
  cricket::ContentAction last_content_action;
  cricket::ContentSource last_content_source;
  std::deque<buzz::XmlElement*> sent_stanzas;
  rtc::scoped_ptr<buzz::XmlElement> last_expected_sent_stanza;

  cricket::SessionManager* session_manager;
  TestSessionClient* client;
  cricket::PortAllocator* port_allocator_;
  cricket::Session* session;
  cricket::BaseSession::State last_session_state;
  rtc::scoped_ptr<ChannelHandler> chan_a;
  rtc::scoped_ptr<ChannelHandler> chan_b;
  bool blow_up_on_error;
  int error_count;
};

class SessionTest : public testing::Test {
 protected:
  virtual void SetUp() {
    // Seed needed for each test to satisfy expectations.
    rtc::SetRandomTestMode(true);
  }

  virtual void TearDown() {
    rtc::SetRandomTestMode(false);
  }

  // Tests sending data between two clients, over two channels.
  void TestSendRecv(ChannelHandler* chan1a,
                    ChannelHandler* chan1b,
                    ChannelHandler* chan2a,
                    ChannelHandler* chan2b) {
    const char* dat1a = "spamspamspamspamspamspamspambakedbeansspam";
    const char* dat2a = "mapssnaebdekabmapsmapsmapsmapsmapsmapsmaps";
    const char* dat1b = "Lobster Thermidor a Crevette with a mornay sauce...";
    const char* dat2b = "...ecuas yanrom a htiw etteverC a rodimrehT retsboL";

    for (int i = 0; i < 20; i++) {
      chan1a->Send(dat1a, strlen(dat1a));
      chan1b->Send(dat1b, strlen(dat1b));
      chan2a->Send(dat2a, strlen(dat2a));
      chan2b->Send(dat2b, strlen(dat2b));

      EXPECT_EQ_WAIT(i + 1, chan1a->data_count, kEventTimeout);
      EXPECT_EQ_WAIT(i + 1, chan1b->data_count, kEventTimeout);
      EXPECT_EQ_WAIT(i + 1, chan2a->data_count, kEventTimeout);
      EXPECT_EQ_WAIT(i + 1, chan2b->data_count, kEventTimeout);

      EXPECT_EQ(strlen(dat2a), chan1a->last_size);
      EXPECT_EQ(strlen(dat2b), chan1b->last_size);
      EXPECT_EQ(strlen(dat1a), chan2a->last_size);
      EXPECT_EQ(strlen(dat1b), chan2b->last_size);

      EXPECT_EQ(0, memcmp(chan1a->last_data, dat2a, strlen(dat2a)));
      EXPECT_EQ(0, memcmp(chan1b->last_data, dat2b, strlen(dat2b)));
      EXPECT_EQ(0, memcmp(chan2a->last_data, dat1a, strlen(dat1a)));
      EXPECT_EQ(0, memcmp(chan2b->last_data, dat1b, strlen(dat1b)));
    }
  }

  // Test an initiate from one client to another, each with
  // independent initial protocols.  Checks for the correct initiates,
  // candidates, and accept messages, and tests that working network
  // channels are established.
  void TestSession(SignalingProtocol initiator_protocol,
                   SignalingProtocol responder_protocol,
                   SignalingProtocol resulting_protocol,
                   const std::string& gingle_content_type,
                   const std::string& content_type,
                   const std::string& content_name_a,
                   const std::string& channel_name_a,
                   const std::string& content_name_b,
                   const std::string& channel_name_b,
                   const std::string& initiate_xml,
                   const std::string& transport_info_a_xml,
                   const std::string& transport_info_b_xml,
                   const std::string& transport_info_reply_a_xml,
                   const std::string& transport_info_reply_b_xml,
                   const std::string& accept_xml,
                   bool bundle = false) {
    rtc::scoped_ptr<cricket::PortAllocator> allocator(
        new TestPortAllocator());
    int next_message_id = 0;

    rtc::scoped_ptr<TestClient> initiator(
        new TestClient(allocator.get(), &next_message_id,
                       kInitiator, initiator_protocol,
                       content_type,
                       content_name_a,  channel_name_a,
                       content_name_b,  channel_name_b));
    rtc::scoped_ptr<TestClient> responder(
        new TestClient(allocator.get(), &next_message_id,
                       kResponder, responder_protocol,
                       content_type,
                       content_name_a,  channel_name_a,
                       content_name_b,  channel_name_b));

    // Create Session and check channels and state.
    initiator->CreateSession();
    EXPECT_EQ(1U, initiator->session_created_count);
    EXPECT_EQ(kSessionId, initiator->session->id());
    EXPECT_EQ(initiator->session->local_name(), kInitiator);
    EXPECT_EQ(cricket::BaseSession::STATE_INIT,
              initiator->session_state());

    // See comment in CreateChannels about how we choose component IDs.
    int component_a = 1;
    int component_b = (content_name_a == content_name_b) ? 2 : 1;
    EXPECT_TRUE(initiator->HasTransport(content_name_a));
    EXPECT_TRUE(initiator->HasChannel(content_name_a, component_a));
    EXPECT_TRUE(initiator->HasTransport(content_name_b));
    EXPECT_TRUE(initiator->HasChannel(content_name_b, component_b));

    // Initiate and expect initiate message sent.
    cricket::SessionDescription* offer = NewTestSessionDescription(
        gingle_content_type,
        content_name_a, content_type,
        content_name_b, content_type);
    if (bundle) {
      cricket::ContentGroup group(cricket::GROUP_TYPE_BUNDLE);
      group.AddContentName(content_name_a);
      group.AddContentName(content_name_b);
      EXPECT_TRUE(group.HasContentName(content_name_a));
      EXPECT_TRUE(group.HasContentName(content_name_b));
      offer->AddGroup(group);
    }
    EXPECT_TRUE(initiator->session->Initiate(kResponder, offer));
    EXPECT_EQ(initiator->session->remote_name(), kResponder);
    EXPECT_EQ(initiator->session->local_description(), offer);

    EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
    EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE,
              initiator->session_state());

    initiator->ExpectSentStanza(
        IqSet("0", kInitiator, kResponder, initiate_xml));

    // Deliver the initiate. Expect ack and session created with
    // transports.
    responder->DeliverStanza(initiator->stanza());
    responder->ExpectSentStanza(
        IqAck("0", kResponder, kInitiator));
    EXPECT_EQ(0U, responder->sent_stanza_count());

    EXPECT_EQ(1U, responder->session_created_count);
    EXPECT_EQ(kSessionId, responder->session->id());
    EXPECT_EQ(responder->session->local_name(), kResponder);
    EXPECT_EQ(responder->session->remote_name(), kInitiator);
    EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDINITIATE,
              responder->session_state());

    EXPECT_TRUE(responder->HasTransport(content_name_a));
    EXPECT_TRUE(responder->HasChannel(content_name_a, component_a));
    EXPECT_TRUE(responder->HasTransport(content_name_b));
    EXPECT_TRUE(responder->HasChannel(content_name_b, component_b));

    // Expect transport-info message from initiator.
    // But don't send candidates until initiate ack is received.
    initiator->PrepareCandidates();
    WAIT(initiator->sent_stanza_count() > 0, 100);
    EXPECT_EQ(0U, initiator->sent_stanza_count());
    initiator->DeliverAckToLastStanza();
    EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
    initiator->ExpectSentStanza(
        IqSet("1", kInitiator, kResponder, transport_info_a_xml));

    // Deliver transport-info and expect ack.
    responder->DeliverStanza(initiator->stanza());
    responder->ExpectSentStanza(
        IqAck("1", kResponder, kInitiator));

    if (!transport_info_b_xml.empty()) {
      // Expect second transport-info message from initiator.
      EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
      initiator->ExpectSentStanza(
          IqSet("2", kInitiator, kResponder, transport_info_b_xml));
      EXPECT_EQ(0U, initiator->sent_stanza_count());

      // Deliver second transport-info message and expect ack.
      responder->DeliverStanza(initiator->stanza());
      responder->ExpectSentStanza(
          IqAck("2", kResponder, kInitiator));
    } else {
      EXPECT_EQ(0U, initiator->sent_stanza_count());
      EXPECT_EQ(0U, responder->sent_stanza_count());
      initiator->SkipUnsentStanza();
    }

    // Expect reply transport-info message from responder.
    responder->PrepareCandidates();
    EXPECT_TRUE_WAIT(responder->sent_stanza_count() > 0, kEventTimeout);
    responder->ExpectSentStanza(
        IqSet("3", kResponder, kInitiator, transport_info_reply_a_xml));

    // Deliver reply transport-info and expect ack.
    initiator->DeliverStanza(responder->stanza());
    initiator->ExpectSentStanza(
        IqAck("3", kInitiator, kResponder));

    if (!transport_info_reply_b_xml.empty()) {
      // Expect second reply transport-info message from responder.
      EXPECT_TRUE_WAIT(responder->sent_stanza_count() > 0, kEventTimeout);
      responder->ExpectSentStanza(
          IqSet("4", kResponder, kInitiator, transport_info_reply_b_xml));
      EXPECT_EQ(0U, responder->sent_stanza_count());

      // Deliver second reply transport-info message and expect ack.
      initiator->DeliverStanza(responder->stanza());
      initiator->ExpectSentStanza(
          IqAck("4", kInitiator, kResponder));
      EXPECT_EQ(0U, initiator->sent_stanza_count());
    } else {
      EXPECT_EQ(0U, initiator->sent_stanza_count());
      EXPECT_EQ(0U, responder->sent_stanza_count());
      responder->SkipUnsentStanza();
    }

    // The channels should be able to become writable at this point.  This
    // requires pinging, so it may take a little while.
    EXPECT_TRUE_WAIT(initiator->chan_a->writable() &&
                     initiator->chan_a->readable(), kEventTimeout);
    EXPECT_TRUE_WAIT(initiator->chan_b->writable() &&
                     initiator->chan_b->readable(), kEventTimeout);
    EXPECT_TRUE_WAIT(responder->chan_a->writable() &&
                     responder->chan_a->readable(), kEventTimeout);
    EXPECT_TRUE_WAIT(responder->chan_b->writable() &&
                     responder->chan_b->readable(), kEventTimeout);

    // Accept the session and expect accept stanza.
    cricket::SessionDescription* answer = NewTestSessionDescription(
        gingle_content_type,
        content_name_a, content_type,
        content_name_b, content_type);
    if (bundle) {
      cricket::ContentGroup group(cricket::GROUP_TYPE_BUNDLE);
      group.AddContentName(content_name_a);
      group.AddContentName(content_name_b);
      EXPECT_TRUE(group.HasContentName(content_name_a));
      EXPECT_TRUE(group.HasContentName(content_name_b));
      answer->AddGroup(group);
    }
    EXPECT_TRUE(responder->session->Accept(answer));
    EXPECT_EQ(responder->session->local_description(), answer);

    responder->ExpectSentStanza(
        IqSet("5", kResponder, kInitiator, accept_xml));

    EXPECT_EQ(0U, responder->sent_stanza_count());

    // Deliver the accept message and expect an ack.
    initiator->DeliverStanza(responder->stanza());
    EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
    initiator->ExpectSentStanza(
        IqAck("5", kInitiator, kResponder));
    EXPECT_EQ(0U, initiator->sent_stanza_count());

    // Both sessions should be in progress and have functioning
    // channels.
    EXPECT_EQ(resulting_protocol, initiator->session->current_protocol());
    EXPECT_EQ(resulting_protocol, responder->session->current_protocol());
    EXPECT_EQ_WAIT(cricket::BaseSession::STATE_INPROGRESS,
                   initiator->session_state(), kEventTimeout);
    EXPECT_EQ_WAIT(cricket::BaseSession::STATE_INPROGRESS,
                   responder->session_state(), kEventTimeout);
    if (bundle) {
      cricket::TransportChannel* initiator_chan_a = initiator->chan_a->channel;
      cricket::TransportChannel* initiator_chan_b = initiator->chan_b->channel;

      // Since we know these are TransportChannelProxy, type cast it.
      cricket::TransportChannelProxy* initiator_proxy_chan_a =
          static_cast<cricket::TransportChannelProxy*>(initiator_chan_a);
      cricket::TransportChannelProxy* initiator_proxy_chan_b =
              static_cast<cricket::TransportChannelProxy*>(initiator_chan_b);
      EXPECT_TRUE(initiator_proxy_chan_a->impl() != NULL);
      EXPECT_TRUE(initiator_proxy_chan_b->impl() != NULL);
      EXPECT_EQ(initiator_proxy_chan_a->impl(), initiator_proxy_chan_b->impl());

      cricket::TransportChannel* responder_chan_a = responder->chan_a->channel;
      cricket::TransportChannel* responder_chan_b = responder->chan_b->channel;

      // Since we know these are TransportChannelProxy, type cast it.
      cricket::TransportChannelProxy* responder_proxy_chan_a =
          static_cast<cricket::TransportChannelProxy*>(responder_chan_a);
      cricket::TransportChannelProxy* responder_proxy_chan_b =
              static_cast<cricket::TransportChannelProxy*>(responder_chan_b);
      EXPECT_TRUE(responder_proxy_chan_a->impl() != NULL);
      EXPECT_TRUE(responder_proxy_chan_b->impl() != NULL);
      EXPECT_EQ(responder_proxy_chan_a->impl(), responder_proxy_chan_b->impl());
    }
    TestSendRecv(initiator->chan_a.get(), initiator->chan_b.get(),
                 responder->chan_a.get(), responder->chan_b.get());

    if (resulting_protocol == PROTOCOL_JINGLE) {
      // Deliver a description-info message to the initiator and check if the
      // content description changes.
      EXPECT_EQ(0U, initiator->session_remote_description_update_count);

      const cricket::SessionDescription* old_session_desc =
          initiator->session->remote_description();
      const cricket::ContentInfo* old_content_a =
          old_session_desc->GetContentByName(content_name_a);
      const cricket::ContentDescription* old_content_desc_a =
          old_content_a->description;
      const cricket::ContentInfo* old_content_b =
          old_session_desc->GetContentByName(content_name_b);
      const cricket::ContentDescription* old_content_desc_b =
          old_content_b->description;
      EXPECT_TRUE(old_content_desc_a != NULL);
      EXPECT_TRUE(old_content_desc_b != NULL);

      LOG(LS_INFO) << "A " << old_content_a->name;
      LOG(LS_INFO) << "B " << old_content_b->name;

      std::string description_info_xml =
          JingleDescriptionInfoXml(content_name_a, content_type);
      initiator->DeliverStanza(
          IqSet("6", kResponder, kInitiator, description_info_xml));
      responder->SkipUnsentStanza();
      EXPECT_EQ(1U, initiator->session_remote_description_update_count);

      const cricket::SessionDescription* new_session_desc =
          initiator->session->remote_description();
      const cricket::ContentInfo* new_content_a =
          new_session_desc->GetContentByName(content_name_a);
      const cricket::ContentDescription* new_content_desc_a =
          new_content_a->description;
      const cricket::ContentInfo* new_content_b =
          new_session_desc->GetContentByName(content_name_b);
      const cricket::ContentDescription* new_content_desc_b =
          new_content_b->description;
      EXPECT_TRUE(new_content_desc_a != NULL);
      EXPECT_TRUE(new_content_desc_b != NULL);

      // TODO: We used to replace contents from an update, but
      // that no longer works with partial updates.  We need to figure out
      // a way to merge patial updates into contents.  For now, users of
      // Session should listen to SignalRemoteDescriptionUpdate and handle
      // updates.  They should not expect remote_description to be the
      // latest value.
      // See session.cc OnDescriptionInfoMessage.

      // EXPECT_NE(old_content_desc_a, new_content_desc_a);

      // if (content_name_a != content_name_b) {
      //   // If content_name_a != content_name_b, then b's content description
      //   // should not have changed since the description-info message only
      //   // contained an update for content_name_a.
      //   EXPECT_EQ(old_content_desc_b, new_content_desc_b);
      // }

      EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
      initiator->ExpectSentStanza(
          IqAck("6", kInitiator, kResponder));
      EXPECT_EQ(0U, initiator->sent_stanza_count());
    } else {
      responder->SkipUnsentStanza();
    }

    initiator->session->Terminate();
    initiator->ExpectSentStanza(
        IqSet("7", kInitiator, kResponder,
              TerminateXml(resulting_protocol,
                           cricket::STR_TERMINATE_SUCCESS)));

    responder->DeliverStanza(initiator->stanza());
    responder->ExpectSentStanza(
        IqAck("7", kResponder, kInitiator));
    EXPECT_EQ(cricket::BaseSession::STATE_SENTTERMINATE,
              initiator->session_state());
    EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDTERMINATE,
              responder->session_state());
  }

  // Test an initiate with other content, called "main".
  void TestOtherContent(SignalingProtocol initiator_protocol,
                        SignalingProtocol responder_protocol,
                        SignalingProtocol resulting_protocol) {
    std::string content_name = "main";
    std::string content_type = "http://oink.splat/session";
    std::string content_name_a = content_name;
    std::string channel_name_a = "rtp";
    std::string content_name_b = content_name;
    std::string channel_name_b = "rtcp";
    std::string initiate_xml = InitiateXml(
        initiator_protocol,
        content_name_a, content_type);
    std::string transport_info_a_xml = TransportInfo4Xml(
        initiator_protocol, content_name,
        channel_name_a, 0, 1,
        channel_name_b, 2, 3);
    std::string transport_info_b_xml = "";
    std::string transport_info_reply_a_xml = TransportInfo4Xml(
        resulting_protocol, content_name,
        channel_name_a, 4, 5,
        channel_name_b, 6, 7);
    std::string transport_info_reply_b_xml = "";
    std::string accept_xml = AcceptXml(
        resulting_protocol,
        content_name_a, content_type);


    TestSession(initiator_protocol, responder_protocol, resulting_protocol,
                content_type,
                content_type,
                content_name_a, channel_name_a,
                content_name_b, channel_name_b,
                initiate_xml,
                transport_info_a_xml, transport_info_b_xml,
                transport_info_reply_a_xml, transport_info_reply_b_xml,
                accept_xml);
  }

  // Test an initiate with audio content.
  void TestAudioContent(SignalingProtocol initiator_protocol,
                        SignalingProtocol responder_protocol,
                        SignalingProtocol resulting_protocol) {
    std::string gingle_content_type = cricket::NS_GINGLE_AUDIO;
    std::string content_name = cricket::CN_AUDIO;
    std::string content_type = cricket::NS_JINGLE_RTP;
    std::string channel_name_a = "rtp";
    std::string channel_name_b = "rtcp";
    std::string initiate_xml = InitiateXml(
        initiator_protocol,
        gingle_content_type,
        content_name, content_type,
        "", "");
    std::string transport_info_a_xml = TransportInfo4Xml(
        initiator_protocol, content_name,
        channel_name_a, 0, 1,
        channel_name_b, 2, 3);
    std::string transport_info_b_xml = "";
    std::string transport_info_reply_a_xml = TransportInfo4Xml(
        resulting_protocol, content_name,
        channel_name_a, 4, 5,
        channel_name_b, 6, 7);
    std::string transport_info_reply_b_xml = "";
    std::string accept_xml = AcceptXml(
        resulting_protocol,
        gingle_content_type,
        content_name, content_type,
        "", "");


    TestSession(initiator_protocol, responder_protocol, resulting_protocol,
                gingle_content_type,
                content_type,
                content_name, channel_name_a,
                content_name, channel_name_b,
                initiate_xml,
                transport_info_a_xml, transport_info_b_xml,
                transport_info_reply_a_xml, transport_info_reply_b_xml,
                accept_xml);
  }

  // Since media content is "split" into two contents (audio and
  // video), we need to treat it special.
  void TestVideoContents(SignalingProtocol initiator_protocol,
                         SignalingProtocol responder_protocol,
                         SignalingProtocol resulting_protocol) {
    std::string content_type = cricket::NS_JINGLE_RTP;
    std::string gingle_content_type = cricket::NS_GINGLE_VIDEO;
    std::string content_name_a = cricket::CN_AUDIO;
    std::string channel_name_a = "rtp";
    std::string content_name_b = cricket::CN_VIDEO;
    std::string channel_name_b = "video_rtp";

    std::string initiate_xml = InitiateXml(
        initiator_protocol,
        gingle_content_type,
        content_name_a, content_type,
        content_name_b, content_type);
    std::string transport_info_a_xml = TransportInfo2Xml(
        initiator_protocol, content_name_a,
        channel_name_a, 0, 1);
    std::string transport_info_b_xml = TransportInfo2Xml(
        initiator_protocol, content_name_b,
        channel_name_b, 2, 3);
    std::string transport_info_reply_a_xml = TransportInfo2Xml(
        resulting_protocol, content_name_a,
        channel_name_a, 4, 5);
    std::string transport_info_reply_b_xml = TransportInfo2Xml(
        resulting_protocol, content_name_b,
        channel_name_b, 6, 7);
    std::string accept_xml = AcceptXml(
        resulting_protocol,
        gingle_content_type,
        content_name_a, content_type,
        content_name_b, content_type);

    TestSession(initiator_protocol, responder_protocol, resulting_protocol,
                gingle_content_type,
                content_type,
                content_name_a, channel_name_a,
                content_name_b, channel_name_b,
                initiate_xml,
                transport_info_a_xml, transport_info_b_xml,
                transport_info_reply_a_xml, transport_info_reply_b_xml,
                accept_xml);
  }

  void TestBadRedirect(SignalingProtocol protocol) {
    std::string content_name = "main";
    std::string content_type = "http://oink.splat/session";
    std::string channel_name_a = "chana";
    std::string channel_name_b = "chanb";
    std::string initiate_xml = InitiateXml(
        protocol, content_name, content_type);
    std::string transport_info_xml = TransportInfo4Xml(
        protocol, content_name,
        channel_name_a, 0, 1,
        channel_name_b, 2, 3);
    std::string transport_info_reply_xml = TransportInfo4Xml(
        protocol, content_name,
        channel_name_a, 4, 5,
        channel_name_b, 6, 7);
    std::string accept_xml = AcceptXml(
        protocol, content_name, content_type);
    std::string responder_full = kResponder + "/full";

    rtc::scoped_ptr<cricket::PortAllocator> allocator(
        new TestPortAllocator());
    int next_message_id = 0;

    rtc::scoped_ptr<TestClient> initiator(
        new TestClient(allocator.get(), &next_message_id,
                       kInitiator, protocol,
                       content_type,
                       content_name, channel_name_a,
                       content_name, channel_name_b));

    rtc::scoped_ptr<TestClient> responder(
        new TestClient(allocator.get(), &next_message_id,
                       responder_full, protocol,
                       content_type,
                       content_name,  channel_name_a,
                       content_name,  channel_name_b));

    // Create Session and check channels and state.
    initiator->CreateSession();
    EXPECT_EQ(1U, initiator->session_created_count);
    EXPECT_EQ(kSessionId, initiator->session->id());
    EXPECT_EQ(initiator->session->local_name(), kInitiator);
    EXPECT_EQ(cricket::BaseSession::STATE_INIT,
              initiator->session_state());

    EXPECT_TRUE(initiator->HasChannel(content_name, 1));
    EXPECT_TRUE(initiator->HasChannel(content_name, 2));

    // Initiate and expect initiate message sent.
    cricket::SessionDescription* offer = NewTestSessionDescription(
        content_name, content_type);
    EXPECT_TRUE(initiator->session->Initiate(kResponder, offer));
    EXPECT_EQ(initiator->session->remote_name(), kResponder);
    EXPECT_EQ(initiator->session->local_description(), offer);

    EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
    EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE,
              initiator->session_state());
    initiator->ExpectSentStanza(
        IqSet("0", kInitiator, kResponder, initiate_xml));

    // Expect transport-info message from initiator.
    initiator->DeliverAckToLastStanza();
    initiator->PrepareCandidates();
    EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
    initiator->ExpectSentStanza(
        IqSet("1", kInitiator, kResponder, transport_info_xml));

    // Send an unauthorized redirect to the initiator and expect it be ignored.
    initiator->blow_up_on_error = false;
    const buzz::XmlElement* initiate_stanza = initiator->stanza();
    rtc::scoped_ptr<buzz::XmlElement> redirect_stanza(
        buzz::XmlElement::ForStr(
            IqError("ER", kResponder, kInitiator,
                    RedirectXml(protocol, initiate_xml, "not@allowed.com"))));
    initiator->session_manager->OnFailedSend(
        initiate_stanza, redirect_stanza.get());
    EXPECT_EQ(initiator->session->remote_name(), kResponder);
    initiator->blow_up_on_error = true;
    EXPECT_EQ(initiator->error_count, 1);
  }

  void TestGoodRedirect(SignalingProtocol protocol) {
    std::string content_name = "main";
    std::string content_type = "http://oink.splat/session";
    std::string channel_name_a = "chana";
    std::string channel_name_b = "chanb";
    std::string initiate_xml = InitiateXml(
        protocol, content_name, content_type);
    std::string transport_info_xml = TransportInfo4Xml(
        protocol, content_name,
        channel_name_a, 0, 1,
        channel_name_b, 2, 3);
    std::string transport_info_reply_xml = TransportInfo4Xml(
        protocol, content_name,
        channel_name_a, 4, 5,
        channel_name_b, 6, 7);
    std::string accept_xml = AcceptXml(
        protocol, content_name, content_type);
    std::string responder_full = kResponder + "/full";

    rtc::scoped_ptr<cricket::PortAllocator> allocator(
        new TestPortAllocator());
    int next_message_id = 0;

    rtc::scoped_ptr<TestClient> initiator(
        new TestClient(allocator.get(), &next_message_id,
                       kInitiator, protocol,
                       content_type,
                       content_name, channel_name_a,
                       content_name, channel_name_b));

    rtc::scoped_ptr<TestClient> responder(
        new TestClient(allocator.get(), &next_message_id,
                       responder_full, protocol,
                       content_type,
                       content_name,  channel_name_a,
                       content_name,  channel_name_b));

    // Create Session and check channels and state.
    initiator->CreateSession();
    EXPECT_EQ(1U, initiator->session_created_count);
    EXPECT_EQ(kSessionId, initiator->session->id());
    EXPECT_EQ(initiator->session->local_name(), kInitiator);
    EXPECT_EQ(cricket::BaseSession::STATE_INIT,
              initiator->session_state());

    EXPECT_TRUE(initiator->HasChannel(content_name, 1));
    EXPECT_TRUE(initiator->HasChannel(content_name, 2));

    // Initiate and expect initiate message sent.
    cricket::SessionDescription* offer = NewTestSessionDescription(
        content_name, content_type);
    EXPECT_TRUE(initiator->session->Initiate(kResponder, offer));
    EXPECT_EQ(initiator->session->remote_name(), kResponder);
    EXPECT_EQ(initiator->session->local_description(), offer);

    EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
    EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE,
              initiator->session_state());
    initiator->ExpectSentStanza(
        IqSet("0", kInitiator, kResponder, initiate_xml));

    // Expect transport-info message from initiator.
    initiator->DeliverAckToLastStanza();
    initiator->PrepareCandidates();
    EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
    initiator->ExpectSentStanza(
        IqSet("1", kInitiator, kResponder, transport_info_xml));

    // Send a redirect to the initiator and expect all of the message
    // to be resent.
    const buzz::XmlElement* initiate_stanza = initiator->stanza();
    rtc::scoped_ptr<buzz::XmlElement> redirect_stanza(
        buzz::XmlElement::ForStr(
            IqError("ER2", kResponder, kInitiator,
                    RedirectXml(protocol, initiate_xml, responder_full))));
    initiator->session_manager->OnFailedSend(
        initiate_stanza, redirect_stanza.get());
    EXPECT_EQ(initiator->session->remote_name(), responder_full);

    EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
    initiator->ExpectSentStanza(
        IqSet("2", kInitiator, responder_full, initiate_xml));
    initiator->ExpectSentStanza(
        IqSet("3", kInitiator, responder_full, transport_info_xml));

    // Deliver the initiate. Expect ack and session created with
    // transports.
    responder->DeliverStanza(
        IqSet("2", kInitiator, responder_full, initiate_xml));
    responder->ExpectSentStanza(
        IqAck("2", responder_full, kInitiator));
    EXPECT_EQ(0U, responder->sent_stanza_count());

    EXPECT_EQ(1U, responder->session_created_count);
    EXPECT_EQ(kSessionId, responder->session->id());
    EXPECT_EQ(responder->session->local_name(), responder_full);
    EXPECT_EQ(responder->session->remote_name(), kInitiator);
    EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDINITIATE,
              responder->session_state());

    EXPECT_TRUE(responder->HasChannel(content_name, 1));
    EXPECT_TRUE(responder->HasChannel(content_name, 2));

    // Deliver transport-info and expect ack.
    responder->DeliverStanza(
        IqSet("3", kInitiator, responder_full, transport_info_xml));
    responder->ExpectSentStanza(
        IqAck("3", responder_full, kInitiator));

    // Expect reply transport-infos sent to new remote JID
    responder->PrepareCandidates();
    EXPECT_TRUE_WAIT(responder->sent_stanza_count() > 0, kEventTimeout);
    responder->ExpectSentStanza(
        IqSet("4", responder_full, kInitiator, transport_info_reply_xml));

    initiator->DeliverStanza(responder->stanza());
    initiator->ExpectSentStanza(
        IqAck("4", kInitiator, responder_full));

    // The channels should be able to become writable at this point.  This
    // requires pinging, so it may take a little while.
    EXPECT_TRUE_WAIT(initiator->chan_a->writable() &&
                     initiator->chan_a->readable(), kEventTimeout);
    EXPECT_TRUE_WAIT(initiator->chan_b->writable() &&
                     initiator->chan_b->readable(), kEventTimeout);
    EXPECT_TRUE_WAIT(responder->chan_a->writable() &&
                     responder->chan_a->readable(), kEventTimeout);
    EXPECT_TRUE_WAIT(responder->chan_b->writable() &&
                     responder->chan_b->readable(), kEventTimeout);

    // Accept the session and expect accept stanza.
    cricket::SessionDescription* answer = NewTestSessionDescription(
        content_name, content_type);
    EXPECT_TRUE(responder->session->Accept(answer));
    EXPECT_EQ(responder->session->local_description(), answer);

    responder->ExpectSentStanza(
        IqSet("5", responder_full, kInitiator, accept_xml));
    EXPECT_EQ(0U, responder->sent_stanza_count());

    // Deliver the accept message and expect an ack.
    initiator->DeliverStanza(responder->stanza());
    EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
    initiator->ExpectSentStanza(
        IqAck("5", kInitiator, responder_full));
    EXPECT_EQ(0U, initiator->sent_stanza_count());

    // Both sessions should be in progress and have functioning
    // channels.
    EXPECT_EQ_WAIT(cricket::BaseSession::STATE_INPROGRESS,
                   initiator->session_state(), kEventTimeout);
    EXPECT_EQ_WAIT(cricket::BaseSession::STATE_INPROGRESS,
                   responder->session_state(), kEventTimeout);
    TestSendRecv(initiator->chan_a.get(), initiator->chan_b.get(),
                 responder->chan_a.get(), responder->chan_b.get());
  }

  void TestCandidatesInInitiateAndAccept(const std::string& test_name) {
    std::string content_name = "main";
    std::string content_type = "http://oink.splat/session";
    std::string channel_name_a = "rtp";
    std::string channel_name_b = "rtcp";
    cricket::SignalingProtocol protocol = PROTOCOL_JINGLE;

    rtc::scoped_ptr<cricket::PortAllocator> allocator(
        new TestPortAllocator());
    int next_message_id = 0;

    rtc::scoped_ptr<TestClient> initiator(
        new TestClient(allocator.get(), &next_message_id,
                       kInitiator, protocol,
                       content_type,
                       content_name,  channel_name_a,
                       content_name,  channel_name_b));

    rtc::scoped_ptr<TestClient> responder(
        new TestClient(allocator.get(), &next_message_id,
                       kResponder, protocol,
                       content_type,
                       content_name,  channel_name_a,
                       content_name,  channel_name_b));

    // Create Session and check channels and state.
    initiator->CreateSession();
    EXPECT_TRUE(initiator->HasTransport(content_name));
    EXPECT_TRUE(initiator->HasChannel(content_name, 1));
    EXPECT_TRUE(initiator->HasTransport(content_name));
    EXPECT_TRUE(initiator->HasChannel(content_name, 2));

    // Initiate and expect initiate message sent.
    cricket::SessionDescription* offer = NewTestSessionDescription(
        content_name, content_type);
    EXPECT_TRUE(initiator->session->Initiate(kResponder, offer));

    EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
    EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE,
              initiator->session_state());
    initiator->ExpectSentStanza(
        IqSet("0", kInitiator, kResponder,
              InitiateXml(protocol, content_name, content_type)));

    // Fake the delivery the initiate and candidates together.
    responder->DeliverStanza(
        IqSet("A", kInitiator, kResponder,
            JingleInitiateActionXml(
                JingleContentXml(
                    content_name, content_type, kTransportType,
                    P2pCandidateXml(channel_name_a, 0) +
                    P2pCandidateXml(channel_name_a, 1) +
                    P2pCandidateXml(channel_name_b, 2) +
                    P2pCandidateXml(channel_name_b, 3)))));
    responder->ExpectSentStanza(
        IqAck("A", kResponder, kInitiator));
    EXPECT_EQ(0U, responder->sent_stanza_count());

    EXPECT_EQ(1U, responder->session_created_count);
    EXPECT_EQ(kSessionId, responder->session->id());
    EXPECT_EQ(responder->session->local_name(), kResponder);
    EXPECT_EQ(responder->session->remote_name(), kInitiator);
    EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDINITIATE,
              responder->session_state());

    EXPECT_TRUE(responder->HasTransport(content_name));
    EXPECT_TRUE(responder->HasChannel(content_name, 1));
    EXPECT_TRUE(responder->HasTransport(content_name));
    EXPECT_TRUE(responder->HasChannel(content_name, 2));

    // Expect transport-info message from initiator.
    // But don't send candidates until initiate ack is received.
    initiator->DeliverAckToLastStanza();
    initiator->PrepareCandidates();
    EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
    initiator->ExpectSentStanza(
        IqSet("1", kInitiator, kResponder,
              TransportInfo4Xml(protocol, content_name,
                                channel_name_a, 0, 1,
                                channel_name_b, 2, 3)));

    responder->PrepareCandidates();
    EXPECT_TRUE_WAIT(responder->sent_stanza_count() > 0, kEventTimeout);
    responder->ExpectSentStanza(
        IqSet("2", kResponder, kInitiator,
              TransportInfo4Xml(protocol, content_name,
                                channel_name_a, 4, 5,
                                channel_name_b, 6, 7)));

    // Accept the session and expect accept stanza.
    cricket::SessionDescription* answer = NewTestSessionDescription(
        content_name, content_type);
    EXPECT_TRUE(responder->session->Accept(answer));

    responder->ExpectSentStanza(
        IqSet("3", kResponder, kInitiator,
              AcceptXml(protocol, content_name, content_type)));
    EXPECT_EQ(0U, responder->sent_stanza_count());

    // Fake the delivery the accept and candidates together.
    initiator->DeliverStanza(
        IqSet("B", kResponder, kInitiator,
            JingleActionXml("session-accept",
                JingleContentXml(
                    content_name, content_type, kTransportType,
                    P2pCandidateXml(channel_name_a, 4) +
                    P2pCandidateXml(channel_name_a, 5) +
                    P2pCandidateXml(channel_name_b, 6) +
                    P2pCandidateXml(channel_name_b, 7)))));
    EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
    initiator->ExpectSentStanza(
        IqAck("B", kInitiator, kResponder));
    EXPECT_EQ(0U, initiator->sent_stanza_count());

    // The channels should be able to become writable at this point.  This
    // requires pinging, so it may take a little while.
    EXPECT_TRUE_WAIT(initiator->chan_a->writable() &&
                     initiator->chan_a->readable(), kEventTimeout);
    EXPECT_TRUE_WAIT(initiator->chan_b->writable() &&
                     initiator->chan_b->readable(), kEventTimeout);
    EXPECT_TRUE_WAIT(responder->chan_a->writable() &&
                     responder->chan_a->readable(), kEventTimeout);
    EXPECT_TRUE_WAIT(responder->chan_b->writable() &&
                     responder->chan_b->readable(), kEventTimeout);


    // Both sessions should be in progress and have functioning
    // channels.
    EXPECT_EQ(protocol, initiator->session->current_protocol());
    EXPECT_EQ(protocol, responder->session->current_protocol());
    EXPECT_EQ_WAIT(cricket::BaseSession::STATE_INPROGRESS,
                   initiator->session_state(), kEventTimeout);
    EXPECT_EQ_WAIT(cricket::BaseSession::STATE_INPROGRESS,
                   responder->session_state(), kEventTimeout);
    TestSendRecv(initiator->chan_a.get(), initiator->chan_b.get(),
                 responder->chan_a.get(), responder->chan_b.get());
  }

  // Tests that when an initiator terminates right after initiate,
  // everything behaves correctly.
  void TestEarlyTerminationFromInitiator(SignalingProtocol protocol) {
    std::string content_name = "main";
    std::string content_type = "http://oink.splat/session";

    rtc::scoped_ptr<cricket::PortAllocator> allocator(
        new TestPortAllocator());
    int next_message_id = 0;

    rtc::scoped_ptr<TestClient> initiator(
        new TestClient(allocator.get(), &next_message_id,
                       kInitiator, protocol,
                       content_type,
                       content_name, "a",
                       content_name, "b"));

    rtc::scoped_ptr<TestClient> responder(
        new TestClient(allocator.get(), &next_message_id,
                       kResponder, protocol,
                       content_type,
                       content_name,  "a",
                       content_name,  "b"));

    // Send initiate
    initiator->CreateSession();
    EXPECT_TRUE(initiator->session->Initiate(
        kResponder, NewTestSessionDescription(content_name, content_type)));
    initiator->ExpectSentStanza(
        IqSet("0", kInitiator, kResponder,
              InitiateXml(protocol, content_name, content_type)));
    EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE,
              initiator->session_state());

    responder->DeliverStanza(initiator->stanza());
    responder->ExpectSentStanza(
        IqAck("0", kResponder, kInitiator));
    EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDINITIATE,
              responder->session_state());

    initiator->session->TerminateWithReason(cricket::STR_TERMINATE_ERROR);
    initiator->ExpectSentStanza(
        IqSet("1", kInitiator, kResponder,
              TerminateXml(protocol, cricket::STR_TERMINATE_ERROR)));
    EXPECT_EQ(cricket::BaseSession::STATE_SENTTERMINATE,
              initiator->session_state());

    responder->DeliverStanza(initiator->stanza());
    responder->ExpectSentStanza(
        IqAck("1", kResponder, kInitiator));
    EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDTERMINATE,
              responder->session_state());
  }

  // Tests that when the responder rejects, everything behaves
  // correctly.
  void TestRejection(SignalingProtocol protocol) {
    std::string content_name = "main";
    std::string content_type = "http://oink.splat/session";

    rtc::scoped_ptr<cricket::PortAllocator> allocator(
        new TestPortAllocator());
    int next_message_id = 0;

    rtc::scoped_ptr<TestClient> initiator(
        new TestClient(allocator.get(), &next_message_id,
                       kInitiator, protocol,
                       content_type,
                       content_name, "a",
                       content_name, "b"));

    // Send initiate
    initiator->CreateSession();
    EXPECT_TRUE(initiator->session->Initiate(
        kResponder, NewTestSessionDescription(content_name, content_type)));
    initiator->ExpectSentStanza(
        IqSet("0", kInitiator, kResponder,
              InitiateXml(protocol, content_name, content_type)));
    EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE,
              initiator->session_state());

    initiator->DeliverStanza(
        IqSet("1", kResponder, kInitiator,
              RejectXml(protocol, cricket::STR_TERMINATE_ERROR)));
    initiator->ExpectSentStanza(
        IqAck("1", kInitiator, kResponder));
    if (protocol == PROTOCOL_JINGLE) {
      EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDTERMINATE,
                initiator->session_state());
    } else {
      EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDREJECT,
                initiator->session_state());
    }
  }

  void TestTransportMux() {
    SignalingProtocol initiator_protocol = PROTOCOL_JINGLE;
    SignalingProtocol responder_protocol = PROTOCOL_JINGLE;
    SignalingProtocol resulting_protocol = PROTOCOL_JINGLE;
    std::string content_type = cricket::NS_JINGLE_RTP;
    std::string gingle_content_type = cricket::NS_GINGLE_VIDEO;
    std::string content_name_a = cricket::CN_AUDIO;
    std::string channel_name_a = "rtp";
    std::string content_name_b = cricket::CN_VIDEO;
    std::string channel_name_b = "video_rtp";

    std::string initiate_xml = InitiateXml(
        initiator_protocol,
        gingle_content_type,
        content_name_a, content_type,
        content_name_b, content_type, true);
    std::string transport_info_a_xml = TransportInfo2Xml(
        initiator_protocol, content_name_a,
        channel_name_a, 0, 1);
    std::string transport_info_b_xml = TransportInfo2Xml(
        initiator_protocol, content_name_b,
        channel_name_b, 2, 3);
    std::string transport_info_reply_a_xml = TransportInfo2Xml(
        resulting_protocol, content_name_a,
        channel_name_a, 4, 5);
    std::string transport_info_reply_b_xml = TransportInfo2Xml(
        resulting_protocol, content_name_b,
        channel_name_b, 6, 7);
    std::string accept_xml = AcceptXml(
        resulting_protocol,
        gingle_content_type,
        content_name_a, content_type,
        content_name_b, content_type, true);

    TestSession(initiator_protocol, responder_protocol, resulting_protocol,
                gingle_content_type,
                content_type,
                content_name_a, channel_name_a,
                content_name_b, channel_name_b,
                initiate_xml,
                transport_info_a_xml, transport_info_b_xml,
                transport_info_reply_a_xml, transport_info_reply_b_xml,
                accept_xml,
                true);
  }

  void TestSendDescriptionInfo() {
    rtc::scoped_ptr<cricket::PortAllocator> allocator(
        new TestPortAllocator());
    int next_message_id = 0;

    std::string content_name = "content-name";
    std::string content_type = "content-type";
    rtc::scoped_ptr<TestClient> initiator(
        new TestClient(allocator.get(), &next_message_id,
                       kInitiator, PROTOCOL_JINGLE,
                       content_type,
                       content_name, "",
                       "",  ""));

    initiator->CreateSession();
    cricket::SessionDescription* offer = NewTestSessionDescription(
        content_name, content_type);
    std::string initiate_xml = InitiateXml(
        PROTOCOL_JINGLE, content_name, content_type);

    cricket::ContentInfos contents;
    TestContentDescription content(content_type, content_type);
    contents.push_back(
        cricket::ContentInfo(content_name, content_type, &content));
    std::string description_info_xml = JingleDescriptionInfoXml(
        content_name, content_type);

    EXPECT_TRUE(initiator->session->Initiate(kResponder, offer));
    initiator->ExpectSentStanza(
        IqSet("0", kInitiator, kResponder, initiate_xml));

    EXPECT_TRUE(initiator->session->SendDescriptionInfoMessage(contents));
    initiator->ExpectSentStanza(
        IqSet("1", kInitiator, kResponder, description_info_xml));
  }

  void DoTestSignalNewDescription(
      TestClient* client,
      cricket::BaseSession::State state,
      cricket::ContentAction expected_content_action,
      cricket::ContentSource expected_content_source) {
    // Clean up before the new test.
    client->new_local_description = false;
    client->new_remote_description = false;

    client->SetSessionState(state);
    EXPECT_EQ((expected_content_source == cricket::CS_LOCAL),
               client->new_local_description);
    EXPECT_EQ((expected_content_source == cricket::CS_REMOTE),
               client->new_remote_description);
    EXPECT_EQ(expected_content_action, client->last_content_action);
    EXPECT_EQ(expected_content_source, client->last_content_source);
  }

  void TestCallerSignalNewDescription() {
    rtc::scoped_ptr<cricket::PortAllocator> allocator(
        new TestPortAllocator());
    int next_message_id = 0;

    std::string content_name = "content-name";
    std::string content_type = "content-type";
    rtc::scoped_ptr<TestClient> initiator(
        new TestClient(allocator.get(), &next_message_id,
                       kInitiator, PROTOCOL_JINGLE,
                       content_type,
                       content_name, "",
                       "",  ""));

    initiator->CreateSession();

    // send offer -> send update offer ->
    // receive pr answer -> receive update pr answer ->
    // receive answer
    DoTestSignalNewDescription(
        initiator.get(), cricket::BaseSession::STATE_SENTINITIATE,
        cricket::CA_OFFER, cricket::CS_LOCAL);

    DoTestSignalNewDescription(
        initiator.get(), cricket::BaseSession::STATE_SENTINITIATE,
        cricket::CA_OFFER, cricket::CS_LOCAL);

    DoTestSignalNewDescription(
        initiator.get(), cricket::BaseSession::STATE_RECEIVEDPRACCEPT,
        cricket::CA_PRANSWER, cricket::CS_REMOTE);

    DoTestSignalNewDescription(
        initiator.get(), cricket::BaseSession::STATE_RECEIVEDPRACCEPT,
        cricket::CA_PRANSWER, cricket::CS_REMOTE);

    DoTestSignalNewDescription(
        initiator.get(), cricket::BaseSession::STATE_RECEIVEDACCEPT,
        cricket::CA_ANSWER, cricket::CS_REMOTE);
  }

  void TestCalleeSignalNewDescription() {
    rtc::scoped_ptr<cricket::PortAllocator> allocator(
        new TestPortAllocator());
    int next_message_id = 0;

    std::string content_name = "content-name";
    std::string content_type = "content-type";
    rtc::scoped_ptr<TestClient> initiator(
        new TestClient(allocator.get(), &next_message_id,
                       kInitiator, PROTOCOL_JINGLE,
                       content_type,
                       content_name, "",
                       "",  ""));

    initiator->CreateSession();

    // receive offer -> receive update offer ->
    // send pr answer -> send update pr answer ->
    // send answer
    DoTestSignalNewDescription(
        initiator.get(), cricket::BaseSession::STATE_RECEIVEDINITIATE,
        cricket::CA_OFFER, cricket::CS_REMOTE);

    DoTestSignalNewDescription(
        initiator.get(), cricket::BaseSession::STATE_RECEIVEDINITIATE,
        cricket::CA_OFFER, cricket::CS_REMOTE);

    DoTestSignalNewDescription(
        initiator.get(), cricket::BaseSession::STATE_SENTPRACCEPT,
        cricket::CA_PRANSWER, cricket::CS_LOCAL);

    DoTestSignalNewDescription(
        initiator.get(), cricket::BaseSession::STATE_SENTPRACCEPT,
        cricket::CA_PRANSWER, cricket::CS_LOCAL);

    DoTestSignalNewDescription(
        initiator.get(), cricket::BaseSession::STATE_SENTACCEPT,
        cricket::CA_ANSWER, cricket::CS_LOCAL);
  }

  void TestGetTransportStats() {
    rtc::scoped_ptr<cricket::PortAllocator> allocator(
        new TestPortAllocator());
    int next_message_id = 0;

    std::string content_name = "content-name";
    std::string content_type = "content-type";
    rtc::scoped_ptr<TestClient> initiator(
        new TestClient(allocator.get(), &next_message_id,
                       kInitiator, PROTOCOL_JINGLE,
                       content_type,
                       content_name, "",
                       "",  ""));
    initiator->CreateSession();

    cricket::SessionStats stats;
    EXPECT_TRUE(initiator->session->GetStats(&stats));
    // At initiation, there are 2 transports.
    EXPECT_EQ(2ul, stats.proxy_to_transport.size());
    EXPECT_EQ(2ul, stats.transport_stats.size());
  }
};

// For each of these, "X => Y = Z" means "if a client with protocol X
// initiates to a client with protocol Y, they end up speaking protocol Z.

// Gingle => Gingle = Gingle (with other content)
TEST_F(SessionTest, GingleToGingleOtherContent) {
  TestOtherContent(PROTOCOL_GINGLE, PROTOCOL_GINGLE, PROTOCOL_GINGLE);
}

// Gingle => Gingle = Gingle (with audio content)
TEST_F(SessionTest, GingleToGingleAudioContent) {
  TestAudioContent(PROTOCOL_GINGLE, PROTOCOL_GINGLE, PROTOCOL_GINGLE);
}

// Gingle => Gingle = Gingle (with video contents)
TEST_F(SessionTest, GingleToGingleVideoContents) {
  TestVideoContents(PROTOCOL_GINGLE, PROTOCOL_GINGLE, PROTOCOL_GINGLE);
}

// Jingle => Jingle = Jingle (with other content)
TEST_F(SessionTest, JingleToJingleOtherContent) {
  TestOtherContent(PROTOCOL_JINGLE, PROTOCOL_JINGLE, PROTOCOL_JINGLE);
}

// Jingle => Jingle = Jingle (with audio content)
TEST_F(SessionTest, JingleToJingleAudioContent) {
  TestAudioContent(PROTOCOL_JINGLE, PROTOCOL_JINGLE, PROTOCOL_JINGLE);
}

// Jingle => Jingle = Jingle (with video contents)
TEST_F(SessionTest, JingleToJingleVideoContents) {
  TestVideoContents(PROTOCOL_JINGLE, PROTOCOL_JINGLE, PROTOCOL_JINGLE);
}

// Hybrid => Hybrid = Jingle (with other content)
TEST_F(SessionTest, HybridToHybridOtherContent) {
  TestOtherContent(PROTOCOL_HYBRID, PROTOCOL_HYBRID, PROTOCOL_JINGLE);
}

// Hybrid => Hybrid = Jingle (with audio content)
TEST_F(SessionTest, HybridToHybridAudioContent) {
  TestAudioContent(PROTOCOL_HYBRID, PROTOCOL_HYBRID, PROTOCOL_JINGLE);
}

// Hybrid => Hybrid = Jingle (with video contents)
TEST_F(SessionTest, HybridToHybridVideoContents) {
  TestVideoContents(PROTOCOL_HYBRID, PROTOCOL_HYBRID, PROTOCOL_JINGLE);
}

// Gingle => Hybrid = Gingle (with other content)
TEST_F(SessionTest, GingleToHybridOtherContent) {
  TestOtherContent(PROTOCOL_GINGLE, PROTOCOL_HYBRID, PROTOCOL_GINGLE);
}

// Gingle => Hybrid = Gingle (with audio content)
TEST_F(SessionTest, GingleToHybridAudioContent) {
  TestAudioContent(PROTOCOL_GINGLE, PROTOCOL_HYBRID, PROTOCOL_GINGLE);
}

// Gingle => Hybrid = Gingle (with video contents)
TEST_F(SessionTest, GingleToHybridVideoContents) {
  TestVideoContents(PROTOCOL_GINGLE, PROTOCOL_HYBRID, PROTOCOL_GINGLE);
}

// Jingle => Hybrid = Jingle (with other content)
TEST_F(SessionTest, JingleToHybridOtherContent) {
  TestOtherContent(PROTOCOL_JINGLE, PROTOCOL_HYBRID, PROTOCOL_JINGLE);
}

// Jingle => Hybrid = Jingle (with audio content)
TEST_F(SessionTest, JingleToHybridAudioContent) {
  TestAudioContent(PROTOCOL_JINGLE, PROTOCOL_HYBRID, PROTOCOL_JINGLE);
}

// Jingle => Hybrid = Jingle (with video contents)
TEST_F(SessionTest, JingleToHybridVideoContents) {
  TestVideoContents(PROTOCOL_JINGLE, PROTOCOL_HYBRID, PROTOCOL_JINGLE);
}

// Hybrid => Gingle = Gingle (with other content)
TEST_F(SessionTest, HybridToGingleOtherContent) {
  TestOtherContent(PROTOCOL_HYBRID, PROTOCOL_GINGLE, PROTOCOL_GINGLE);
}

// Hybrid => Gingle = Gingle (with audio content)
TEST_F(SessionTest, HybridToGingleAudioContent) {
  TestAudioContent(PROTOCOL_HYBRID, PROTOCOL_GINGLE, PROTOCOL_GINGLE);
}

// Hybrid => Gingle = Gingle (with video contents)
TEST_F(SessionTest, HybridToGingleVideoContents) {
  TestVideoContents(PROTOCOL_HYBRID, PROTOCOL_GINGLE, PROTOCOL_GINGLE);
}

// Hybrid => Jingle = Jingle (with other content)
TEST_F(SessionTest, HybridToJingleOtherContent) {
  TestOtherContent(PROTOCOL_HYBRID, PROTOCOL_JINGLE, PROTOCOL_JINGLE);
}

// Hybrid => Jingle = Jingle (with audio content)
TEST_F(SessionTest, HybridToJingleAudioContent) {
  TestAudioContent(PROTOCOL_HYBRID, PROTOCOL_JINGLE, PROTOCOL_JINGLE);
}

// Hybrid => Jingle = Jingle (with video contents)
TEST_F(SessionTest, HybridToJingleVideoContents) {
  TestVideoContents(PROTOCOL_HYBRID, PROTOCOL_JINGLE, PROTOCOL_JINGLE);
}

TEST_F(SessionTest, GingleEarlyTerminationFromInitiator) {
  TestEarlyTerminationFromInitiator(PROTOCOL_GINGLE);
}

TEST_F(SessionTest, JingleEarlyTerminationFromInitiator) {
  TestEarlyTerminationFromInitiator(PROTOCOL_JINGLE);
}

TEST_F(SessionTest, HybridEarlyTerminationFromInitiator) {
  TestEarlyTerminationFromInitiator(PROTOCOL_HYBRID);
}

TEST_F(SessionTest, GingleRejection) {
  TestRejection(PROTOCOL_GINGLE);
}

TEST_F(SessionTest, JingleRejection) {
  TestRejection(PROTOCOL_JINGLE);
}

TEST_F(SessionTest, GingleGoodRedirect) {
  TestGoodRedirect(PROTOCOL_GINGLE);
}

TEST_F(SessionTest, JingleGoodRedirect) {
  TestGoodRedirect(PROTOCOL_JINGLE);
}

TEST_F(SessionTest, GingleBadRedirect) {
  TestBadRedirect(PROTOCOL_GINGLE);
}

TEST_F(SessionTest, JingleBadRedirect) {
  TestBadRedirect(PROTOCOL_JINGLE);
}

TEST_F(SessionTest, TestCandidatesInInitiateAndAccept) {
  TestCandidatesInInitiateAndAccept("Candidates in initiate/accept");
}

TEST_F(SessionTest, TestTransportMux) {
  TestTransportMux();
}

TEST_F(SessionTest, TestSendDescriptionInfo) {
  TestSendDescriptionInfo();
}

TEST_F(SessionTest, TestCallerSignalNewDescription) {
  TestCallerSignalNewDescription();
}

TEST_F(SessionTest, TestCalleeSignalNewDescription) {
  TestCalleeSignalNewDescription();
}

TEST_F(SessionTest, TestGetTransportStats) {
  TestGetTransportStats();
}
