/*
 * 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>
#include <vector>

#include "talk/media/base/fakemediaengine.h"
#include "talk/media/base/testutils.h"
#include "talk/media/devices/fakedevicemanager.h"
#include "talk/p2p/base/constants.h"
#include "talk/p2p/client/basicportallocator.h"
#include "talk/session/media/mediasessionclient.h"
#include "talk/xmllite/xmlbuilder.h"
#include "talk/xmllite/xmlelement.h"
#include "talk/xmllite/xmlprinter.h"
#include "talk/xmpp/constants.h"
#include "webrtc/base/gunit.h"
#include "webrtc/base/logging.h"
#include "webrtc/base/scoped_ptr.h"

using cricket::AudioCodec;
using cricket::AudioContentDescription;
using cricket::Codec;
using cricket::DataCodec;
using cricket::DataContentDescription;
using cricket::FeedbackParam;
using cricket::FeedbackParams;
using cricket::VideoCodec;
using cricket::VideoContentDescription;

// The codecs that our FakeMediaEngine will support. Order is important, since
// the tests check that our messages have codecs in the correct order.
static const cricket::AudioCodec kAudioCodecs[] = {
  cricket::AudioCodec(103, "ISAC",   16000, -1,    1, 18),
  cricket::AudioCodec(104, "ISAC",   32000, -1,    1, 17),
  cricket::AudioCodec(119, "ISACLC", 16000, 40000, 1, 16),
  cricket::AudioCodec(99,  "speex",  16000, 22000, 1, 15),
  cricket::AudioCodec(97,  "IPCMWB", 16000, 80000, 1, 14),
  cricket::AudioCodec(9,   "G722",   16000, 64000, 1, 13),
  cricket::AudioCodec(102, "iLBC",   8000,  13300, 1, 12),
  cricket::AudioCodec(98,  "speex",  8000,  11000, 1, 11),
  cricket::AudioCodec(3,   "GSM",    8000,  13000, 1, 10),
  cricket::AudioCodec(100, "EG711U", 8000,  64000, 1, 9),
  cricket::AudioCodec(101, "EG711A", 8000,  64000, 1, 8),
  cricket::AudioCodec(0,   "PCMU",   8000,  64000, 1, 7),
  cricket::AudioCodec(8,   "PCMA",   8000,  64000, 1, 6),
  cricket::AudioCodec(126, "CN",     32000, 0,     1, 5),
  cricket::AudioCodec(105, "CN",     16000, 0,     1, 4),
  cricket::AudioCodec(13,  "CN",     8000,  0,     1, 3),
  cricket::AudioCodec(117, "red",    8000,  0,     1, 2),
  cricket::AudioCodec(106, "telephone-event", 8000, 0, 1, 1)
};

// The codecs that our FakeMediaEngine will support with a different order of
// supported codecs.
static const cricket::AudioCodec kAudioCodecsDifferentPreference[] = {
  cricket::AudioCodec(104, "ISAC",   32000, -1,    1, 17),
  cricket::AudioCodec(97,  "IPCMWB", 16000, 80000, 1, 14),
  cricket::AudioCodec(9,   "G722",   16000, 64000, 1, 13),
  cricket::AudioCodec(119, "ISACLC", 16000, 40000, 1, 16),
  cricket::AudioCodec(103, "ISAC",   16000, -1,    1, 18),
  cricket::AudioCodec(99,  "speex",  16000, 22000, 1, 15),
  cricket::AudioCodec(100, "EG711U", 8000,  64000, 1, 9),
  cricket::AudioCodec(101, "EG711A", 8000,  64000, 1, 8),
  cricket::AudioCodec(0,   "PCMU",   8000,  64000, 1, 7),
  cricket::AudioCodec(8,   "PCMA",   8000,  64000, 1, 6),
  cricket::AudioCodec(102, "iLBC",   8000,  13300, 1, 12),
  cricket::AudioCodec(3,   "GSM",    8000,  13000, 1, 10),
  cricket::AudioCodec(98,  "speex",  8000,  11000, 1, 11),
  cricket::AudioCodec(126, "CN",     32000, 0,     1, 5),
  cricket::AudioCodec(105, "CN",     16000, 0,     1, 4),
  cricket::AudioCodec(13,  "CN",     8000,  0,     1, 3),
  cricket::AudioCodec(117, "red",    8000,  0,     1, 2),
  cricket::AudioCodec(106, "telephone-event", 8000, 0, 1, 1)
};

static const cricket::VideoCodec kVideoCodecs[] = {
  cricket::VideoCodec(96, "H264-SVC", 320, 200, 30, 1)
};

static const cricket::DataCodec kDataCodecs[] = {
  cricket::DataCodec(127, "google-data", 0)
};

const std::string kGingleCryptoOffer = \
    "<rtp:encryption xmlns:rtp='urn:xmpp:jingle:apps:rtp:1'>   "  \
    "  <usage/>                                                "  \
    "  <rtp:crypto tag='145' crypto-suite='AES_CM_128_HMAC_SHA1_32'" \
    "  key-params='inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9'/>" \
    "  <rtp:crypto tag='51' crypto-suite='AES_CM_128_HMAC_SHA1_80'" \
    "  key-params='inline:J4lfdUL8W1F7TNJKcbuygaQuA429SJy2e9JctPUy'/>" \
    "</rtp:encryption>                                         ";

// Jingle offer does not have any <usage> element.
const std::string kJingleCryptoOffer = \
    "<rtp:encryption xmlns:rtp='urn:xmpp:jingle:apps:rtp:1'>   "  \
    "  <rtp:crypto tag='145' crypto-suite='AES_CM_128_HMAC_SHA1_32'" \
    "  key-params='inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9'/>" \
    "  <rtp:crypto tag='51' crypto-suite='AES_CM_128_HMAC_SHA1_80'" \
    "  key-params='inline:J4lfdUL8W1F7TNJKcbuygaQuA429SJy2e9JctPUy'/>" \
    "</rtp:encryption>                                         ";


const std::string kGingleRequiredCryptoOffer = \
    "<rtp:encryption xmlns:rtp='urn:xmpp:jingle:apps:rtp:1' required='true'> "\
    "  <usage/>                                                "  \
    "  <rtp:crypto tag='145' crypto-suite='AES_CM_128_HMAC_SHA1_32'" \
    "  key-params='inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9'/>" \
    "  <rtp:crypto tag='51' crypto-suite='AES_CM_128_HMAC_SHA1_80'" \
    "  key-params='inline:J4lfdUL8W1F7TNJKcbuygaQuA429SJy2e9JctPUy'/>" \
    "</rtp:encryption>                                         ";

const std::string kJingleRequiredCryptoOffer = \
    "<rtp:encryption xmlns:rtp='urn:xmpp:jingle:apps:rtp:1' required='true'> "\
    "  <rtp:crypto tag='145' crypto-suite='AES_CM_128_HMAC_SHA1_32'" \
    "  key-params='inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9'/>" \
    "  <rtp:crypto tag='51' crypto-suite='AES_CM_128_HMAC_SHA1_80'" \
    "  key-params='inline:J4lfdUL8W1F7TNJKcbuygaQuA429SJy2e9JctPUy'/>" \
    "</rtp:encryption>                                         ";


const std::string kGingleUnsupportedCryptoOffer = \
    "<rtp:encryption xmlns:rtp='urn:xmpp:jingle:apps:rtp:1'>   "  \
    "  <usage/>                                                "  \
    "  <rtp:crypto tag='145' crypto-suite='NOT_SUPPORTED_1'" \
    "  key-params='inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9'/>" \
    "  <rtp:crypto tag='51' crypto-suite='NOT_SUPPORTED_2'" \
    "  key-params='inline:J4lfdUL8W1F7TNJKcbuygaQuA429SJy2e9JctPUy'/>" \
    "</rtp:encryption>                                         ";

const std::string kJingleUnsupportedCryptoOffer =                 \
    "<rtp:encryption xmlns:rtp='urn:xmpp:jingle:apps:rtp:1'>   "  \
    "  <rtp:crypto tag='145' crypto-suite='NOT_SUPPORTED_1'" \
    "  key-params='inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9'/>" \
    "  <rtp:crypto tag='51' crypto-suite='NOT_SUPPORTED_2'" \
    "  key-params='inline:J4lfdUL8W1F7TNJKcbuygaQuA429SJy2e9JctPUy'/>" \
    "</rtp:encryption>                                         ";


// With unsupported but with required="true"
const std::string kGingleRequiredUnsupportedCryptoOffer =         \
    "<rtp:encryption xmlns:rtp='urn:xmpp:jingle:apps:rtp:1' required='true'>" \
    "  <usage/>                                                "  \
    "  <rtp:crypto tag='145' crypto-suite='NOT_SUPPORTED_1'"      \
    "  key-params='inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9'/>" \
    "  <rtp:crypto tag='51' crypto-suite='NOT_SUPPORTED_2'" \
    "  key-params='inline:J4lfdUL8W1F7TNJKcbuygaQuA429SJy2e9JctPUy'/>" \
    "</rtp:encryption>                                         ";

const std::string kJingleRequiredUnsupportedCryptoOffer =                     \
    "<rtp:encryption xmlns:rtp='urn:xmpp:jingle:apps:rtp:1' required='true'>" \
    "  <rtp:crypto tag='145' crypto-suite='NOT_SUPPORTED_1'                 " \
    "  key-params='inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9'/>       " \
    "  <rtp:crypto tag='51' crypto-suite='NOT_SUPPORTED_2'                  " \
    "  key-params='inline:J4lfdUL8W1F7TNJKcbuygaQuA429SJy2e9JctPUy'/>"        \
    "</rtp:encryption>                                         ";

const std::string kGingleInitiate(
     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
     "    to='user@domain.com/resource' type='set' id='123'>          " \
     "  <session xmlns='http://www.google.com/session' type='initiate'" \
     "    id='abcdef' initiator='me@domain.com/resource'>             " \
     "    <description xmlns='http://www.google.com/session/phone'>   " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='103' name='ISAC' clockrate='16000' />               " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='104' name='ISAC' clockrate='32000' />               " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='119' name='ISACLC' clockrate='16000' bitrate='40000' />  " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='99' name='speex' clockrate='16000' bitrate='22000' />    " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='97' name='IPCMWB' clockrate='16000' bitrate='80000' />   " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='9' name='G722' clockrate='16000' bitrate='64000' /> " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='102' name='iLBC' clockrate='8000' bitrate='13300' />" \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='98' name='speex' clockrate='8000' bitrate='11000' />" \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='3' name='GSM' clockrate='8000' bitrate='13000' />   " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='100' name='EG711U' clockrate='8000' bitrate='64000' />   " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='101' name='EG711A' clockrate='8000' bitrate='64000' />   " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='0' name='PCMU' clockrate='8000' bitrate='64000' />  " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='8' name='PCMA' clockrate='8000' bitrate='64000' />  " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='126' name='CN' clockrate='32000' />                 " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='105' name='CN' clockrate='16000' />                 " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='13' name='CN' clockrate='8000' />                   " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='117' name='red' clockrate='8000' />                 " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='106' name='telephone-event' clockrate='8000' />     " \
     "    </description>                                              " \
     "  </session>                                                    " \
     "</iq>                                                           ");

const std::string kJingleInitiate(
     "<iq xmlns='jabber:client' from='me@domain.com/resource'           " \
     "    to='user@domain.com/resource' type='set' id='123'>            " \
     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'     " \
     "          sid='abcdef' initiator='me@domain.com/resource'>        " \
     "    <content name='test audio'>                                   " \
     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'> " \
     "        <payload-type id='103' name='ISAC' clockrate='16000'/>    " \
     "        <payload-type id='104' name='ISAC' clockrate='32000'/>    " \
     "        <payload-type                                             " \
     "          id='119' name='ISACLC' clockrate='16000'>               " \
     "          <parameter name='bitrate' value='40000'/>               " \
     "        </payload-type>                                           " \
     "        <payload-type                                             " \
     "          id='99' name='speex' clockrate='16000'>                 " \
     "          <parameter name='bitrate' value='22000'/>               " \
     "        </payload-type>                                           " \
     "        <payload-type                                             " \
     "          id='97' name='IPCMWB' clockrate='16000'>                " \
     "          <parameter name='bitrate' value='80000'/>               " \
     "        </payload-type>                                           " \
     "        <payload-type                                             " \
     "          id='9' name='G722' clockrate='16000'>                   " \
     "          <parameter name='bitrate' value='64000'/>               " \
     "        </payload-type>                                           " \
     "        <payload-type                                             " \
     "          id='102' name='iLBC' clockrate='8000'>                  " \
     "          <parameter name='bitrate' value='13300'/>               " \
     "        </payload-type>                                           " \
     "        <payload-type                                             " \
     "          id='98' name='speex' clockrate='8000'>                  " \
     "          <parameter name='bitrate' value='11000'/>               " \
     "        </payload-type>                                           " \
     "        <payload-type                                             " \
     "          id='3' name='GSM' clockrate='8000'>                     " \
     "          <parameter name='bitrate' value='13000'/>               " \
     "        </payload-type>                                           " \
     "        <payload-type                                             " \
     "          id='100' name='EG711U' clockrate='8000'>                " \
     "          <parameter name='bitrate' value='64000'/>               " \
     "        </payload-type>                                           " \
     "        <payload-type                                             " \
     "          id='101' name='EG711A' clockrate='8000'>                " \
     "          <parameter name='bitrate' value='64000'/>               " \
     "        </payload-type>                                           " \
     "        <payload-type                                             " \
     "          id='0' name='PCMU' clockrate='8000'>                    " \
     "          <parameter name='bitrate' value='64000'/>               " \
     "        </payload-type>                                           " \
     "        <payload-type                                             " \
     "          id='8' name='PCMA' clockrate='8000'>                    " \
     "          <parameter name='bitrate' value='64000'/>               " \
     "        </payload-type>                                           " \
     "        <payload-type                                             " \
     "          id='126' name='CN' clockrate='32000' />                 " \
     "        <payload-type                                             " \
     "          id='105' name='CN' clockrate='16000' />                 " \
     "        <payload-type                                             " \
     "          id='13' name='CN' clockrate='8000' />                   " \
     "        <payload-type                                             " \
     "          id='117' name='red' clockrate='8000' />                 " \
     "        <payload-type                                             " \
     "          id='106' name='telephone-event' clockrate='8000' />     " \
     "      </description>                                              " \
     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
     "    </content>                                                    " \
     "  </jingle>                                                       " \
     "</iq>                                                             ");

const std::string kJingleInitiateWithRtcpFb(
     "<iq xmlns='jabber:client' from='me@domain.com/resource'           " \
     "    to='user@domain.com/resource' type='set' id='123'>            " \
     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'     " \
     "         sid='abcdef' initiator='me@domain.com/resource'>         " \
     "    <content name='test audio'>                                   " \
     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'> " \
     "        <payload-type id='103' name='ISAC' clockrate='16000'>     " \
     "          <rtcp-fb type='nack'/>                                  " \
     "        </payload-type>                                           " \
     "      </description>                                              " \
     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
     "    </content>                                                    " \
     "    <content name='test video'>                                   " \
     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='video'> " \
     "        <rtcp-fb type='nack'/>                                    " \
     "        <payload-type id='99' name='H264-SVC'>                    " \
     "          <rtcp-fb type='ccm' subtype='fir'/>                    " \
     "          <parameter name='height' value='200'/>                  " \
     "          <parameter name='width' value='320'/>                   " \
     "          <parameter name='framerate' value='30'/>                " \
     "        </payload-type>                                           " \
     "      </description>                                              " \
     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
     "    </content>                                                    " \
     "    <content name='test data'>                                    " \
     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='data'> " \
     "        <rtcp-fb type='nack'/>                                    " \
     "        <payload-type id='127' name='google-data'>                " \
     "        </payload-type>                                           " \
     "      </description>                                              " \
     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
     "    </content>                                                    " \
     "  </jingle>                                                       " \
     "</iq>                                                             ");

const std::string kGingleVideoInitiate(
     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
     "    to='user@domain.com/resource' type='set' id='123'>          " \
     "  <session xmlns='http://www.google.com/session' type='initiate'" \
     "    id='abcdef' initiator='me@domain.com/resource'>             " \
     "    <description xmlns='http://www.google.com/session/video'>   " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='103' name='ISAC' clockrate='16000' />               " \
     "      <payload-type xmlns='http://www.google.com/session/video' " \
     "        id='99' name='H264-SVC' framerate='30'                  " \
     "        height='200' width='320'/>                              " \
     "    </description>                                              " \
     "  </session>                                                    " \
     "</iq>                                                           ");

const std::string kJingleVideoInitiate(
     "<iq xmlns='jabber:client' from='me@domain.com/resource'           " \
     "    to='user@domain.com/resource' type='set' id='123'>            " \
     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'     " \
     "          sid='abcdef' initiator='me@domain.com/resource'>        " \
     "    <content name='test audio'>                                   " \
     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'> " \
     "        <payload-type id='103' name='ISAC' clockrate='16000'/>    " \
     "      </description>                                              " \
     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
     "    </content>                                                    " \
     "    <content name='test video'>                                   " \
     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='video'> " \
     "        <payload-type id='99' name='H264-SVC'>                    " \
     "          <parameter name='height' value='200'/>                  " \
     "          <parameter name='width' value='320'/>                   " \
     "          <parameter name='framerate' value='30'/>                " \
     "        </payload-type>                                           " \
     "      </description>                                              " \
     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
     "    </content>                                                    " \
     "  </jingle>                                                       " \
     "</iq>                                                             ");

const std::string kJingleVideoInitiateWithRtpData(
     "<iq xmlns='jabber:client' from='me@domain.com/resource'           " \
     "    to='user@domain.com/resource' type='set' id='123'>            " \
     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'     " \
     "          sid='abcdef' initiator='me@domain.com/resource'>        " \
     "    <content name='test audio'>                                   " \
     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'> " \
     "        <payload-type id='103' name='ISAC' clockrate='16000'/>    " \
     "      </description>                                              " \
     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
     "    </content>                                                    " \
     "    <content name='test video'>                                   " \
     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='video'> " \
     "        <payload-type id='99' name='H264-SVC'>                    " \
     "          <parameter name='height' value='200'/>                  " \
     "          <parameter name='width' value='320'/>                   " \
     "          <parameter name='framerate' value='30'/>                " \
     "        </payload-type>                                           " \
     "      </description>                                              " \
     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
     "    </content>                                                    " \
     "    <content name='test data'>                                    " \
     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='data'> " \
     "        <payload-type id='127' name='google-data'/>               " \
     "        <rtcp-mux/>                                               " \
     "      </description>                                              " \
     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
     "    </content>                                                    " \
     "  </jingle>                                                       " \
     "</iq>                                                             ");

const std::string kJingleVideoInitiateWithSctpData(
     "<iq xmlns='jabber:client' from='me@domain.com/resource'           " \
     "    to='user@domain.com/resource' type='set' id='123'>            " \
     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'     " \
     "          sid='abcdef' initiator='me@domain.com/resource'>        " \
     "    <content name='test audio'>                                   " \
     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'> " \
     "        <payload-type id='103' name='ISAC' clockrate='16000'/>    " \
     "      </description>                                              " \
     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
     "    </content>                                                    " \
     "    <content name='test video'>                                   " \
     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='video'> " \
     "        <payload-type id='99' name='H264-SVC'>                    " \
     "          <parameter name='height' value='200'/>                  " \
     "          <parameter name='width' value='320'/>                   " \
     "          <parameter name='framerate' value='30'/>                " \
     "        </payload-type>                                           " \
     "      </description>                                              " \
     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
     "    </content>                                                    " \
     "    <content name='test data'>                                    " \
     "      <description xmlns='google:jingle:sctp' media='data'>       " \
     "        <stream sid='1'/>                                         " \
     "      </description>                                              " \
     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
     "    </content>                                                    " \
     "  </jingle>                                                       " \
     "</iq>                                                             ");

const std::string kJingleVideoInitiateWithBandwidth(
     "<iq xmlns='jabber:client' from='me@domain.com/resource'           " \
     "    to='user@domain.com/resource' type='set' id='123'>            " \
     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'     " \
     "         sid='abcdef' initiator='me@domain.com/resource'>         " \
     "    <content name='test audio'>                                   " \
     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'> " \
     "        <payload-type id='103' name='ISAC' clockrate='16000'/>    " \
     "      </description>                                              " \
     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
     "    </content>                                                    " \
     "    <content name='test video'>                                   " \
     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='video'> " \
     "        <payload-type id='99' name='H264-SVC'>                    " \
     "          <parameter name='height' value='200'/>                  " \
     "          <parameter name='width' value='320'/>                   " \
     "          <parameter name='framerate' value='30'/>                " \
     "        </payload-type>                                           " \
     "        <bandwidth type='AS'>42</bandwidth>                       " \
     "      </description>                                              " \
     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
     "    </content>                                                    " \
     "  </jingle>                                                       " \
     "</iq>                                                             ");

const std::string kJingleVideoInitiateWithRtcpMux(
     "<iq xmlns='jabber:client' from='me@domain.com/resource'           " \
     "    to='user@domain.com/resource' type='set' id='123'>            " \
     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'     " \
     "         sid='abcdef' initiator='me@domain.com/resource'>         " \
     "    <content name='test audio'>                                   " \
     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'> " \
     "        <payload-type id='103' name='ISAC' clockrate='16000'/>    " \
     "      </description>                                              " \
     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
     "    </content>                                                    " \
     "    <content name='test video'>                                   " \
     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='video'> " \
     "        <payload-type id='99' name='H264-SVC'>                    " \
     "          <parameter name='height' value='200'/>                  " \
     "          <parameter name='width' value='320'/>                   " \
     "          <parameter name='framerate' value='30'/>                " \
     "        </payload-type>                                           " \
     "        <rtcp-mux/>                                               " \
     "      </description>                                              " \
     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
     "    </content>                                                    " \
     "  </jingle>                                                       " \
     "</iq>                                                             ");

// Initiate string with a combination of supported and unsupported codecs
// Should accept the supported ones
const std::string kGingleInitiateSomeUnsupported(
     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
     "    to='user@domain.com/resource' type='set' id='123'>          " \
     "  <session xmlns='http://www.google.com/session' type='initiate'" \
     "    id='abcdef' initiator='me@domain.com/resource'>             " \
     "    <description xmlns='http://www.google.com/session/phone'>   " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='103' name='ISAC' clockrate='16000' />               " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='97' name='ASDFDS' />                                " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='102' name='1010' />                                 " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='107' name='DFAS' />                                 " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='100' name='EG711U' />                               " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='101' name='EG711A' />                               " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='0' name='PCMU' />                                   " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='110' name=':)' />                                   " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='13' name='CN' />                                    " \
     "    </description>                                              " \
     "  </session>                                                    " \
     "</iq>                                                           ");

const std::string kJingleInitiateSomeUnsupported(
     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
     "    to='user@domain.com/resource' type='set' id='123'>          " \
     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'   " \
     "          sid='abcdef' initiator='me@domain.com/resource'>      " \
     "    <content name='test audio'>                                 " \
     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'> " \
     "        <payload-type                                           " \
     "          id='103' name='ISAC' clockrate='16000' />             " \
     "        <payload-type                                           " \
     "          id='97' name='ASDFDS' />                              " \
     "        <payload-type                                           " \
     "          id='102' name='1010' />                               " \
     "        <payload-type                                           " \
     "          id='107' name='DFAS' />                               " \
     "        <payload-type                                           " \
     "          id='100' name='EG711U' />                             " \
     "        <payload-type                                           " \
     "          id='101' name='EG711A' />                             " \
     "        <payload-type                                           " \
     "          id='0' name='PCMU' />                                 " \
     "        <payload-type                                           " \
     "          id='110' name=':)' />                                 " \
     "        <payload-type                                           " \
     "          id='13' name='CN' />                                  " \
     "      </description>                                            " \
     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/> " \
     "    </content>                                                  " \
     "  </jingle>                                                     " \
     "</iq>                                                           ");

const std::string kGingleVideoInitiateWithBandwidth(
     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
     "    to='user@domain.com/resource' type='set' id='123'>          " \
     "  <session xmlns='http://www.google.com/session' type='initiate'" \
     "    id='abcdef' initiator='me@domain.com/resource'>             " \
     "    <description xmlns='http://www.google.com/session/video'>   " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='103' name='ISAC' clockrate='16000' />               " \
     "      <payload-type xmlns='http://www.google.com/session/video' " \
     "        id='99' name='H264-SVC' framerate='30'                  " \
     "        height='200' width='320'/>                              " \
     "      <bandwidth type='AS'>42</bandwidth>                       " \
     "    </description>                                              " \
     "  </session>                                                    " \
     "</iq>                                                           ");

// Initiate string without any supported codecs. Should send a reject.
const std::string kGingleInitiateNoSupportedAudioCodecs(
     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
     "    to='user@domain.com/resource' type='set' id='123'>          " \
     "  <session xmlns='http://www.google.com/session' type='initiate'" \
     "    id='abcdef' initiator='me@domain.com/resource'>             " \
     "    <description xmlns='http://www.google.com/session/phone'>   " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='123' name='Supercodec6000' />                       " \
     "    </description>                                              " \
     "  </session>                                                    " \
     "</iq>                                                           ");

const std::string kJingleInitiateNoSupportedAudioCodecs(
     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
     "    to='user@domain.com/resource' type='set' id='123'>          " \
     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'   " \
     "          sid='abcdef' initiator='me@domain.com/resource'>      " \
     "    <content name='test audio'>                                 " \
     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'>" \
     "        <payload-type                                           " \
     "          id='123' name='Supercodec6000' />                     " \
     "      </description>                                            " \
     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>  " \
     "    </content>                                                  " \
     "  </jingle>                                                     " \
     "</iq>                                                           ");

// Initiate string without any codecs. Assumes ancient version of Cricket
// and tries a session with ISAC and PCMU
const std::string kGingleInitiateNoAudioCodecs(
     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
     "    to='user@domain.com/resource' type='set' id='123'>          " \
     "  <session xmlns='http://www.google.com/session' type='initiate'" \
     "    id='abcdef' initiator='me@domain.com/resource'>             " \
     "    <description xmlns='http://www.google.com/session/phone'>   " \
     "    </description>                                              " \
     "  </session>                                                    " \
     "</iq>                                                           ");

const std::string kJingleInitiateNoAudioCodecs(
     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
     "    to='user@domain.com/resource' type='set' id='123'>          " \
     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'   " \
     "          sid='abcdef' initiator='me@domain.com/resource'>      " \
     "    <content name='test audio'>                                 " \
     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'>" \
     "      </description>                                            " \
     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>  " \
     "    </content>                                                  " \
     "  </jingle>                                                     " \
     "</iq>                                                           ");

// The codecs are supported, but not at the given clockrates. Should send
// a reject.
const std::string kGingleInitiateWrongClockrates(
     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
     "    to='user@domain.com/resource' type='set' id='123'>          " \
     "  <session xmlns='http://www.google.com/session' type='initiate'" \
     "    id='abcdef' initiator='me@domain.com/resource'>             " \
     "    <description xmlns='http://www.google.com/session/phone'>   " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='103' name='ISAC' clockrate='8000'/>                 " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='97' name='IPCMWB' clockrate='1337'/>                " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='102' name='iLBC' clockrate='1982' />                " \
     "    </description>                                              " \
     "  </session>                                                    " \
     "</iq>                                                           ");

const std::string kJingleInitiateWrongClockrates(
     "<iq xmlns='jabber:client' from='me@domain.com/resource'           " \
     "    to='user@domain.com/resource' type='set' id='123'>            " \
     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'     " \
     "          sid='abcdef' initiator='me@domain.com/resource'>        " \
     "    <content name='test audio'>                                   " \
     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'>" \
     "        <payload-type                                             " \
     "          id='103' name='ISAC' clockrate='8000'/>                 " \
     "        <payload-type                                             " \
     "          id='97' name='IPCMWB' clockrate='1337'/>                " \
     "       <payload-type                                              " \
     "          id='102' name='iLBC' clockrate='1982' />                " \
     "      </description>                                              " \
     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>  " \
     "    </content>                                                    " \
     "  </jingle>                                                       " \
     "</iq>                                                             ");

// The codecs are supported, but not with the given number of channels.
// Should send a reject.
const std::string kGingleInitiateWrongChannels(
     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
     "    to='user@domain.com/resource' type='set' id='123'>          " \
     "  <session xmlns='http://www.google.com/session' type='initiate'" \
     "    id='abcdef' initiator='me@domain.com/resource'>             " \
     "    <description xmlns='http://www.google.com/session/phone'>   " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='103' name='ISAC' channels='2'/>                     " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='97' name='IPCMWB' channels='3'/>                    " \
     "    </description>                                              " \
     "  </session>                                                    " \
     "</iq>                                                           ");

const std::string kJingleInitiateWrongChannels(
     "<iq xmlns='jabber:client' from='me@domain.com/resource'           " \
     "    to='user@domain.com/resource' type='set' id='123'>            " \
     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'>    " \
     "    <content name='test audio'>                                   " \
     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'>" \
     "        <payload-type                                             " \
     "          id='103' name='ISAC' channels='2'/>                     " \
     "        <payload-type                                             " \
     "          id='97' name='IPCMWB' channels='3'/>                    " \
     "      </description>                                              " \
     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
     "    </content>                                                    " \
     "  </jingle>                                                       " \
     "</iq>                                                             ");

// Initiate with a dynamic codec not using webrtc default payload id. Should
// accept with provided payload id.
const std::string kGingleInitiateDynamicAudioCodecs(
     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
     "    to='user@domain.com/resource' type='set' id='123'>          " \
     "  <session xmlns='http://www.google.com/session' type='initiate'" \
     "    id='abcdef' initiator='me@domain.com/resource'>             " \
     "    <description xmlns='http://www.google.com/session/phone'>   " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='123' name='speex' clockrate='16000'/>               " \
     "    </description>                                              " \
     "  </session>                                                    " \
     "</iq>                                                           ");

const std::string kJingleInitiateDynamicAudioCodecs(
     "<iq xmlns='jabber:client' from='me@domain.com/resource'           " \
     "    to='user@domain.com/resource' type='set' id='123'>            " \
     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'     " \
     "          sid='abcdef' initiator='me@domain.com/resource'>        " \
     "    <content name='test audio'>                                   " \
     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'>" \
     "        <payload-type                                             " \
     "          id='123' name='speex' clockrate='16000'/>               " \
     "      </description>                                              " \
     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
     "    </content>                                                    " \
     "  </jingle>                                                       " \
     "</iq>                                                             ");

// Initiate string with nothing but static codec id's. Should accept.
const std::string kGingleInitiateStaticAudioCodecs(
     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
     "    to='user@domain.com/resource' type='set' id='123'>          " \
     "  <session xmlns='http://www.google.com/session' type='initiate'" \
     "    id='abcdef' initiator='me@domain.com/resource'>             " \
     "    <description xmlns='http://www.google.com/session/phone'>   " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='3' />                                               " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='0' />                                               " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='8' />                                               " \
     "    </description>                                              " \
     "  </session>                                                    " \
     "</iq>                                                           ");

const std::string kJingleInitiateStaticAudioCodecs(
     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
     "    to='user@domain.com/resource' type='set' id='123'>          " \
     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'   " \
     "          sid='abcdef' initiator='me@domain.com/resource'>      " \
     "    <content name='test audio'>                                 " \
     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'>" \
     "        <payload-type id='3' />                                 " \
     "        <payload-type id='0' />                                 " \
     "        <payload-type id='8' />                                 " \
     "      </description>                                            " \
     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/> " \
     "    </content>                                                  " \
     "  </jingle>                                                     " \
     "</iq>                                                           ");

// Initiate with payload type-less codecs. Should reject.
const std::string kGingleInitiateNoPayloadTypes(
     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
     "    to='user@domain.com/resource' type='set' id='123'>          " \
     "  <session xmlns='http://www.google.com/session' type='initiate'" \
     "    id='abcdef' initiator='me@domain.com/resource'>             " \
     "    <description xmlns='http://www.google.com/session/phone'>   " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "       name='ISAC' clockrate='16000'/>                          " \
     "    </description>                                              " \
     "  </session>                                                    " \
     "</iq>                                                           ");

const std::string kJingleInitiateNoPayloadTypes(
     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
     "    to='user@domain.com/resource' type='set' id='123'>          " \
     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'>  " \
     "          sid='abcdef' initiator='me@domain.com/resource'>      " \
     "    <content name='test audio'>                                 " \
     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'>" \
     "        <payload-type  name='ISAC' clockrate='16000'/>          " \
     "      </description>                                            " \
     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/> " \
     "    </content>                                                  " \
     "  </jingle>                                                     " \
     "</iq>                                                           ");

// Initiate with unnamed dynamic codces. Should reject.
const std::string kGingleInitiateDynamicWithoutNames(
     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
     "    to='user@domain.com/resource' type='set' id='123'>          " \
     "  <session xmlns='http://www.google.com/session' type='initiate'" \
     "    id='abcdef' initiator='me@domain.com/resource'>             " \
     "    <description xmlns='http://www.google.com/session/phone'>   " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "       id='100' clockrate='16000'/>                             " \
     "    </description>                                              " \
     "  </session>                                                    " \
     "</iq>                                                           ");

const std::string kJingleInitiateDynamicWithoutNames(
     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
     "    to='user@domain.com/resource' type='set' id='123'>          " \
     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'>  " \
     "          sid='abcdef' initiator='me@domain.com/resource'>      " \
     "    <content name='test audio'>                                 " \
     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'>" \
     "        <payload-type id='100' clockrate='16000'/>              " \
     "      </description>                                            " \
     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>  " \
     "    </content>                                                  " \
     "  </jingle>                                                     " \
     "</iq>                                                           ");

const uint32 kAudioSsrc = 4294967295U;
const uint32 kVideoSsrc = 87654321;
const uint32 kDataSsrc = 1010101;
// Note that this message does not specify a session ID. It must be populated
// before use.
const std::string kGingleAcceptWithSsrcs(
     "<iq xmlns='jabber:client' from='me@mydomain.com'                " \
     "    to='user@domain.com/resource' type='set' id='150'>          " \
     "  <session xmlns='http://www.google.com/session' type='accept'  " \
     "    initiator='me@domain.com/resource'>                         " \
     "    <description xmlns='http://www.google.com/session/video'>   " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='103' name='ISAC' clockrate='16000' />               " \
     "      <payload-type xmlns='http://www.google.com/session/phone' " \
     "        id='104' name='ISAC' clockrate='32000' />               " \
     "      <src-id xmlns='http://www.google.com/session/phone'>      " \
     "        4294967295</src-id>                                       " \
     "      <src-id>87654321</src-id>                                 " \
     "    </description>                                              " \
     "  </session>                                                    " \
     "</iq>                                                           ");

const std::string kJingleAcceptWithSsrcs(
     "<iq xmlns='jabber:client' from='me@mydomain.com'                " \
     "    to='user@domain.com/resource' type='set' id='150'>          " \
     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-accept'     " \
     "          initiator='me@domain.com/resource'>                   " \
     "    <content name='audio'>                                      " \
     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1'           " \
     "          media='audio' ssrc='4294967295'>                      " \
     "        <payload-type id='103' name='ISAC' clockrate='16000'/>  " \
     "        <payload-type id='104' name='ISAC' clockrate='32000'/>  " \
     "      </description>                                            " \
     "     <transport xmlns='http://www.google.com/transport/p2p'/>   " \
     "    </content>                                                  " \
     "    <content name='video'>                                      " \
     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1'           " \
     "          media='video' ssrc='87654321'>                        " \
     "      </description>                                            " \
     "     <transport xmlns='http://www.google.com/transport/p2p'/>   " \
     "    </content>                                                  " \
     "  </jingle>                                                     " \
     "</iq>                                                           ");

const std::string kJingleAcceptWithRtpDataSsrcs(
     "<iq xmlns='jabber:client' from='me@mydomain.com'                " \
     "    to='user@domain.com/resource' type='set' id='150'>          " \
     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-accept'     " \
     "          initiator='me@domain.com/resource'>                   " \
     "    <content name='audio'>                                      " \
     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1'           " \
     "          media='audio' ssrc='4294967295'>                      " \
     "        <payload-type id='103' name='ISAC' clockrate='16000'/>  " \
     "        <payload-type id='104' name='ISAC' clockrate='32000'/>  " \
     "      </description>                                            " \
     "     <transport xmlns='http://www.google.com/transport/p2p'/>   " \
     "    </content>                                                  " \
     "    <content name='video'>                                      " \
     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1'           " \
     "          media='video' ssrc='87654321'>                        " \
     "      </description>                                            " \
     "     <transport xmlns='http://www.google.com/transport/p2p'/>   " \
     "    </content>                                                  " \
     "    <content name='data'>                                       " \
     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1'           " \
     "          media='data' ssrc='1010101'>                          " \
     "      </description>                                            " \
     "     <transport xmlns='http://www.google.com/transport/p2p'/>   " \
     "    </content>                                                  " \
     "  </jingle>                                                     " \
     "</iq>                                                           ");

const std::string kJingleAcceptWithSctpData(
     "<iq xmlns='jabber:client' from='me@mydomain.com'                " \
     "    to='user@domain.com/resource' type='set' id='150'>          " \
     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-accept'     " \
     "          initiator='me@domain.com/resource'>                   " \
     "    <content name='audio'>                                      " \
     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1'           " \
     "          media='audio' ssrc='4294967295'>                      " \
     "        <payload-type id='103' name='ISAC' clockrate='16000'/>  " \
     "        <payload-type id='104' name='ISAC' clockrate='32000'/>  " \
     "      </description>                                            " \
     "     <transport xmlns='http://www.google.com/transport/p2p'/>   " \
     "    </content>                                                  " \
     "    <content name='video'>                                      " \
     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1'           " \
     "          media='video' ssrc='87654321'>                        " \
     "      </description>                                            " \
     "     <transport xmlns='http://www.google.com/transport/p2p'/>   " \
     "    </content>                                                  " \
     "    <content name='data'>                                       " \
     "      <description xmlns='google:jingle:sctp'>                  " \
     "        <stream sid='1'/>                                       " \
     "      </description>                                            " \
     "     <transport xmlns='http://www.google.com/transport/p2p'/>   " \
     "    </content>                                                  " \
     "  </jingle>                                                     " \
     "</iq>                                                           ");

std::string JingleView(const std::string& ssrc,
                       const std::string& width,
                       const std::string& height,
                       const std::string& framerate) {
  // We have some slightly weird whitespace formatting to make the
  // actual XML generated match the expected XML here.
  return \
      "<cli:iq"
      "  to='me@mydomain.com'"
      "  type='set'"
      "  xmlns:cli='jabber:client'>"
        "<jingle"
      "    xmlns='urn:xmpp:jingle:1'"
      "    action='session-info'"
      "    sid=''>"
          "<view xmlns='google:jingle'"
      "      name='video'"
      "      type='static'"
      "      ssrc='" + ssrc + "'>"
            "<params"
      "        width='" + width + "'"
      "        height='" + height + "'"
      "        framerate='" + framerate + "'"
      "        preference='0'/>"
          "</view>"
        "</jingle>"
      "</cli:iq>";
}

std::string JingleStreamAdd(const std::string& content_name,
                            const std::string& nick,
                            const std::string& name,
                            const std::string& ssrc) {
  return \
      "<iq"
      "  xmlns='jabber:client'"
      "  from='me@mydomain.com'"
      "  to='user@domain.com/resource'"
      "  type='set'"
      "  id='150'>"
      "  <jingle"
      "    xmlns='urn:xmpp:jingle:1'"
      "    action='description-info'>"
      "    <content"
      "      xmlns='urn:xmpp:jingle:1'"
      "      name='" + content_name + "'>"
      "      <description"
      "        xmlns='urn:xmpp:jingle:apps:rtp:1'"
      "        media='" + content_name + "'>"
      "        <streams"
      "          xmlns='google:jingle'>"
      "          <stream"
      "            nick='" + nick + "'"
      "            name='" + name + "'>"
      "            <ssrc>"  + ssrc + "</ssrc>"
      "          </stream>"
      "        </streams>"
      "      </description>"
      "    </content>"
      "  </jingle>"
      "</iq>";
}

std::string JingleOutboundStreamRemove(const std::string& sid,
                                       const std::string& content_name,
                                       const std::string& name) {
  return \
      "<cli:iq"
      " to='me@mydomain.com'"
      " type='set'"
      " xmlns:cli='jabber:client'>"
      "<jingle"
      " xmlns='urn:xmpp:jingle:1'"
      " action='description-info'"
      " sid='" + sid + "'>"
      "<content"
      " name='" + content_name + "'"
      " creator='initiator'>"
      "<description"
      " xmlns='urn:xmpp:jingle:apps:rtp:1'"
      " media='" + content_name + "'>"
      "<streams"
      " xmlns='google:jingle'>"
      "<stream"
      " name='" + name + "'>"
      "</stream>"
      "</streams>"
      "</description>"
      "</content>"
      "</jingle>"
      "</cli:iq>";
}

std::string JingleOutboundStreamAdd(const std::string& sid,
                                    const std::string& content_name,
                                    const std::string& name,
                                    const std::string& ssrc) {
  return \
      "<cli:iq"
      " to='me@mydomain.com'"
      " type='set'"
      " xmlns:cli='jabber:client'>"
      "<jingle"
      " xmlns='urn:xmpp:jingle:1'"
      " action='description-info'"
      " sid='" + sid + "'>"
      "<content"
      " name='" + content_name + "'"
      " creator='initiator'>"
      "<description"
      " xmlns='urn:xmpp:jingle:apps:rtp:1'"
      " media='" + content_name + "'>"
      "<streams"
      " xmlns='google:jingle'>"
      "<stream"
      " name='" + name + "'>"
      "<ssrc>" + ssrc + "</ssrc>"
      "</stream>"
      "</streams>"
      "</description>"
      "</content>"
      "</jingle>"
      "</cli:iq>";
}

std::string JingleStreamAddWithoutSsrc(const std::string& content_name,
                                       const std::string& nick,
                                       const std::string& name) {
  return \
      "<iq"
      "  xmlns='jabber:client'"
      "  from='me@mydomain.com'"
      "  to='user@domain.com/resource'"
      "  type='set'"
      "  id='150'>"
      "  <jingle"
      "    xmlns='urn:xmpp:jingle:1'"
      "    action='description-info'>"
      "    <content"
      "      xmlns='urn:xmpp:jingle:1'"
      "      name='" + content_name + "'>"
      "      <description"
      "        xmlns='urn:xmpp:jingle:apps:rtp:1'"
      "        media='" + content_name + "'>"
      "        <streams"
      "          xmlns='google:jingle'>"
      "          <stream"
      "            nick='" + nick + "'"
      "            name='" + name + "'>"
       "          </stream>"
      "        </streams>"
      "      </description>"
      "    </content>"
      "  </jingle>"
      "</iq>";
}

std::string JingleStreamRemove(const std::string& content_name,
                               const std::string& nick,
                               const std::string& name) {
  return \
      "<iq"
      "  xmlns='jabber:client'"
      "  from='me@mydomain.com'"
      "  to='user@domain.com/resource'"
      "  type='set'"
      "  id='150'>"
      "  <jingle"
      "    xmlns='urn:xmpp:jingle:1'"
      "    action='description-info'>"
      "    <content"
      "      xmlns='urn:xmpp:jingle:1'"
      "      name='" + content_name + "'>"
      "      <description"
      "        xmlns='urn:xmpp:jingle:apps:rtp:1'"
      "        media='" + content_name + "'>"
      "        <streams"
      "          xmlns='google:jingle'>"
      "          <stream"
      "            nick='" + nick + "'"
      "            name='" + name + "'/>"
      "        </streams>"
      "      </description>"
      "    </content>"
      "  </jingle>"
      "</iq>";
}

// Convenience function to get CallOptions that have audio enabled,
// but not video or data.
static cricket::CallOptions AudioCallOptions() {
  cricket::CallOptions options;
  options.has_audio = true;
  options.has_video = false;
  options.data_channel_type = cricket::DCT_NONE;
  return options;
}

// Convenience function to get CallOptions that have audio and video
// enabled, but not data.
static cricket::CallOptions VideoCallOptions() {
  cricket::CallOptions options;
  options.has_audio = true;
  options.has_video = true;
  options.data_channel_type = cricket::DCT_NONE;
  return options;
}

static buzz::XmlElement* CopyElement(const buzz::XmlElement* elem) {
  return new buzz::XmlElement(*elem);
}

static std::string AddEncryption(std::string stanza, std::string encryption) {
  std::string::size_type pos = stanza.find("</description>");
  while (pos != std::string::npos) {
      stanza = stanza.insert(pos, encryption);
      pos = stanza.find("</description>", pos + encryption.length() + 1);
  }
  return stanza;
}

static int IntFromJingleCodecParameter(const buzz::XmlElement* parameter,
                                       const std::string& expected_name) {
  if (parameter) {
    const std::string& actual_name =
        parameter->Attr(cricket::QN_PAYLOADTYPE_PARAMETER_NAME);

    EXPECT_EQ(expected_name, actual_name)
        << "wrong parameter name.  Expected '"
        << expected_name << "'. Actually '"
        << actual_name << "'.";

    return atoi(parameter->Attr(
        cricket::QN_PAYLOADTYPE_PARAMETER_VALUE).c_str());
  }
  return 0;
}

template <class CodecClass, class DescriptionClass>
static void VerifyCodecFbParams(const FeedbackParams& expected,
                                const DescriptionClass* desc) {
  if (!expected.params().empty()) {
    ASSERT_TRUE(desc != NULL);
    const std::vector<CodecClass> codecs = desc->codecs();
    for (size_t i = 0; i < codecs.size(); ++i) {
      EXPECT_EQ(expected, codecs[i].feedback_params);
    }
  }
}

// Parses and extracts payload and codec info from test XML.  Since
// that XML will be in various contents (Gingle and Jingle), we need an
// abstract parser with one concrete implementation per XML content.
class MediaSessionTestParser {
 public:
  virtual buzz::XmlElement* ActionFromStanza(buzz::XmlElement* stanza) = 0;
  virtual buzz::XmlElement* ContentFromAction(buzz::XmlElement* action) = 0;
  virtual buzz::XmlElement* NextContent(buzz::XmlElement* content) = 0;
  virtual buzz::XmlElement* PayloadTypeFromContent(
      buzz::XmlElement* content) = 0;
  virtual buzz::XmlElement* NextFromPayloadType(
      buzz::XmlElement* payload_type) = 0;
  virtual cricket::AudioCodec AudioCodecFromPayloadType(
      const buzz::XmlElement* payload_type) = 0;
  virtual cricket::VideoCodec VideoCodecFromPayloadType(
      const buzz::XmlElement* payload_type) = 0;
  virtual cricket::DataCodec DataCodecFromPayloadType(
      const buzz::XmlElement* payload_type) = 0;
  virtual buzz::XmlElement* EncryptionFromContent(
      buzz::XmlElement* content) = 0;
  virtual buzz::XmlElement* NextFromEncryption(
      buzz::XmlElement* encryption) = 0;
  virtual const buzz::XmlElement* BandwidthFromContent(
      buzz::XmlElement* content) = 0;
  virtual const buzz::XmlElement* RtcpMuxFromContent(
      buzz::XmlElement* content) = 0;
  virtual bool ActionIsTerminate(const buzz::XmlElement* action) = 0;
  virtual ~MediaSessionTestParser() {}
};

class JingleSessionTestParser : public MediaSessionTestParser {
 public:
  JingleSessionTestParser() {}

  ~JingleSessionTestParser() {
  }

  buzz::XmlElement* ActionFromStanza(buzz::XmlElement* stanza) {
    return stanza->FirstNamed(cricket::QN_JINGLE);
  }

  buzz::XmlElement* ContentFromAction(buzz::XmlElement* action) {
    // We need to be able to use multiple contents, but the action
    // gets deleted before we can call NextContent, so we need to
    // stash away a copy.
    action_.reset(CopyElement(action));
    return action_->FirstNamed(cricket::QN_JINGLE_CONTENT);
  }

  buzz::XmlElement* NextContent(buzz::XmlElement* content) {
    // For some reason, content->NextNamed(cricket::QN_JINGLE_CONTENT)
    // doesn't work.
    return action_->FirstNamed(cricket::QN_JINGLE_CONTENT)
        ->NextNamed(cricket::QN_JINGLE_CONTENT);
  }

  buzz::XmlElement* PayloadTypeFromContent(buzz::XmlElement* content) {
    buzz::XmlElement* content_desc =
        content->FirstNamed(cricket::QN_JINGLE_RTP_CONTENT);
    if (!content_desc)
      return NULL;

    return content_desc->FirstNamed(cricket::QN_JINGLE_RTP_PAYLOADTYPE);
  }

  buzz::XmlElement* NextFromPayloadType(buzz::XmlElement* payload_type) {
    return payload_type->NextNamed(cricket::QN_JINGLE_RTP_PAYLOADTYPE);
  }

  void ParsePayloadTypeFeedbackParameters(const buzz::XmlElement* element,
                                          FeedbackParams* params) {
    const buzz::XmlElement* param =
        element->FirstNamed(cricket::QN_JINGLE_RTCP_FB);
    for (; param != NULL;
         param = param->NextNamed(cricket::QN_JINGLE_RTCP_FB)) {
      std::string type  = param->Attr(cricket::QN_TYPE);
      std::string subtype = param->Attr(cricket::QN_SUBTYPE);
      if (!type.empty()) {
        params->Add(FeedbackParam(type, subtype));
      }
    }
  }

  cricket::AudioCodec AudioCodecFromPayloadType(
      const buzz::XmlElement* payload_type) {
    int id = 0;
    if (payload_type->HasAttr(cricket::QN_ID))
      id = atoi(payload_type->Attr(cricket::QN_ID).c_str());

    std::string name;
    if (payload_type->HasAttr(cricket::QN_NAME))
      name = payload_type->Attr(cricket::QN_NAME);

    int clockrate = 0;
    if (payload_type->HasAttr(cricket::QN_CLOCKRATE))
      clockrate = atoi(payload_type->Attr(cricket::QN_CLOCKRATE).c_str());

    int bitrate = IntFromJingleCodecParameter(
        payload_type->FirstNamed(cricket::QN_PARAMETER), "bitrate");

    int channels = 1;
    if (payload_type->HasAttr(cricket::QN_CHANNELS))
      channels = atoi(payload_type->Attr(
          cricket::QN_CHANNELS).c_str());

    AudioCodec codec = AudioCodec(id, name, clockrate, bitrate, channels, 0);
    ParsePayloadTypeFeedbackParameters(payload_type, &codec.feedback_params);
    return codec;
  }

  cricket::VideoCodec VideoCodecFromPayloadType(
      const buzz::XmlElement* payload_type) {
    int id = 0;
    if (payload_type->HasAttr(cricket::QN_ID))
      id = atoi(payload_type->Attr(cricket::QN_ID).c_str());

    std::string name;
    if (payload_type->HasAttr(cricket::QN_NAME))
      name = payload_type->Attr(cricket::QN_NAME);

    int width = 0;
    int height = 0;
    int framerate = 0;
    const buzz::XmlElement* param =
        payload_type->FirstNamed(cricket::QN_PARAMETER);
    if (param) {
      width = IntFromJingleCodecParameter(param, "width");
      param = param->NextNamed(cricket::QN_PARAMETER);
      if (param) {
        height = IntFromJingleCodecParameter(param, "height");
        param = param->NextNamed(cricket::QN_PARAMETER);
        if (param) {
          framerate = IntFromJingleCodecParameter(param, "framerate");
        }
      }
    }
    VideoCodec codec = VideoCodec(id, name, width, height, framerate, 0);
    ParsePayloadTypeFeedbackParameters(payload_type, &codec.feedback_params);
    return codec;
  }

  cricket::DataCodec DataCodecFromPayloadType(
      const buzz::XmlElement* payload_type) {
    int id = 0;
    if (payload_type->HasAttr(cricket::QN_ID))
      id = atoi(payload_type->Attr(cricket::QN_ID).c_str());

    std::string name;
    if (payload_type->HasAttr(cricket::QN_NAME))
      name = payload_type->Attr(cricket::QN_NAME);

    DataCodec codec = DataCodec(id, name, 0);
    ParsePayloadTypeFeedbackParameters(payload_type, &codec.feedback_params);
    return codec;
  }

  bool ActionIsTerminate(const buzz::XmlElement* action) {
    return (action->HasAttr(cricket::QN_ACTION) &&
            action->Attr(cricket::QN_ACTION) == "session-terminate");
  }

  buzz::XmlElement* EncryptionFromContent(buzz::XmlElement* content) {
    buzz::XmlElement* content_desc =
        content->FirstNamed(cricket::QN_JINGLE_RTP_CONTENT);
    if (!content_desc)
      return NULL;

    return content_desc->FirstNamed(cricket::QN_ENCRYPTION);
  }

  buzz::XmlElement* NextFromEncryption(buzz::XmlElement* encryption) {
    return encryption->NextNamed(cricket::QN_ENCRYPTION);
  }

  const buzz::XmlElement* BandwidthFromContent(buzz::XmlElement* content) {
    buzz::XmlElement* content_desc =
        content->FirstNamed(cricket::QN_JINGLE_RTP_CONTENT);
    if (!content_desc)
      return NULL;

    return content_desc->FirstNamed(cricket::QN_JINGLE_RTP_BANDWIDTH);
  }

  const buzz::XmlElement* RtcpMuxFromContent(buzz::XmlElement* content) {
    return content->FirstNamed(cricket::QN_JINGLE_RTCP_MUX);
  }

 private:
  rtc::scoped_ptr<buzz::XmlElement> action_;
};

class GingleSessionTestParser : public MediaSessionTestParser {
 public:
  GingleSessionTestParser() : found_content_count_(0) {}

  buzz::XmlElement* ActionFromStanza(buzz::XmlElement* stanza) {
    return stanza->FirstNamed(cricket::QN_GINGLE_SESSION);
  }

  buzz::XmlElement* ContentFromAction(buzz::XmlElement* session) {
    buzz::XmlElement* content =
        session->FirstNamed(cricket::QN_GINGLE_AUDIO_CONTENT);
    if (content == NULL)
      content = session->FirstNamed(cricket::QN_GINGLE_VIDEO_CONTENT);
    return content;
  }

  // Assumes contents are in order of audio, and then video.
  buzz::XmlElement* NextContent(buzz::XmlElement* content) {
    found_content_count_++;
    return content;
  }

  buzz::XmlElement* PayloadTypeFromContent(buzz::XmlElement* content) {
    if (found_content_count_ > 0) {
      return content->FirstNamed(cricket::QN_GINGLE_VIDEO_PAYLOADTYPE);
    } else {
      return content->FirstNamed(cricket::QN_GINGLE_AUDIO_PAYLOADTYPE);
    }
  }

  buzz::XmlElement* NextFromPayloadType(buzz::XmlElement* payload_type) {
    if (found_content_count_ > 0) {
      return payload_type->NextNamed(cricket::QN_GINGLE_VIDEO_PAYLOADTYPE);
    } else {
      return payload_type->NextNamed(cricket::QN_GINGLE_AUDIO_PAYLOADTYPE);
    }
  }

  cricket::AudioCodec AudioCodecFromPayloadType(
      const buzz::XmlElement* payload_type) {
    int id = 0;
    if (payload_type->HasAttr(cricket::QN_ID))
      id = atoi(payload_type->Attr(cricket::QN_ID).c_str());

    std::string name;
    if (payload_type->HasAttr(cricket::QN_NAME))
      name = payload_type->Attr(cricket::QN_NAME);

    int clockrate = 0;
    if (payload_type->HasAttr(cricket::QN_CLOCKRATE))
      clockrate = atoi(payload_type->Attr(cricket::QN_CLOCKRATE).c_str());

    int bitrate = 0;
    if (payload_type->HasAttr(cricket::QN_BITRATE))
      bitrate = atoi(payload_type->Attr(cricket::QN_BITRATE).c_str());

    int channels = 1;
    if (payload_type->HasAttr(cricket::QN_CHANNELS))
      channels = atoi(payload_type->Attr(cricket::QN_CHANNELS).c_str());

    return cricket::AudioCodec(id, name, clockrate, bitrate, channels, 0);
  }

  cricket::VideoCodec VideoCodecFromPayloadType(
      const buzz::XmlElement* payload_type) {
    int id = 0;
    if (payload_type->HasAttr(cricket::QN_ID))
      id = atoi(payload_type->Attr(cricket::QN_ID).c_str());

    std::string name;
    if (payload_type->HasAttr(cricket::QN_NAME))
      name = payload_type->Attr(cricket::QN_NAME);

    int width = 0;
    if (payload_type->HasAttr(cricket::QN_WIDTH))
      width = atoi(payload_type->Attr(cricket::QN_WIDTH).c_str());

    int height = 0;
    if (payload_type->HasAttr(cricket::QN_HEIGHT))
      height = atoi(payload_type->Attr(cricket::QN_HEIGHT).c_str());

    int framerate = 1;
    if (payload_type->HasAttr(cricket::QN_FRAMERATE))
      framerate = atoi(payload_type->Attr(cricket::QN_FRAMERATE).c_str());

    return cricket::VideoCodec(id, name, width, height, framerate, 0);
  }

  cricket::DataCodec DataCodecFromPayloadType(
      const buzz::XmlElement* payload_type) {
    // Gingle can't do data codecs.
    return cricket::DataCodec(0, "", 0);
  }

  buzz::XmlElement* EncryptionFromContent(
      buzz::XmlElement* content) {
    return content->FirstNamed(cricket::QN_ENCRYPTION);
  }

  buzz::XmlElement* NextFromEncryption(buzz::XmlElement* encryption) {
    return encryption->NextNamed(cricket::QN_ENCRYPTION);
  }

  const buzz::XmlElement* BandwidthFromContent(buzz::XmlElement* content) {
    return content->FirstNamed(cricket::QN_GINGLE_VIDEO_BANDWIDTH);
  }

  const buzz::XmlElement* RtcpMuxFromContent(buzz::XmlElement* content) {
    return NULL;
  }

  bool ActionIsTerminate(const buzz::XmlElement* session) {
    return (session->HasAttr(buzz::QN_TYPE) &&
            session->Attr(buzz::QN_TYPE) == "terminate");
  }

  int found_content_count_;
};

class MediaSessionClientTest : public sigslot::has_slots<> {
 public:
  explicit MediaSessionClientTest(MediaSessionTestParser* parser,
                                  cricket::SignalingProtocol initial_protocol) {
    nm_ = new rtc::BasicNetworkManager();
    pa_ = new cricket::BasicPortAllocator(nm_);
    sm_ = new cricket::SessionManager(pa_, NULL);
    fme_ = new cricket::FakeMediaEngine();
    fdme_ = new cricket::FakeDataEngine();

    FeedbackParams params_nack_fir;
    params_nack_fir.Add(FeedbackParam(cricket::kRtcpFbParamCcm,
                                      cricket::kRtcpFbCcmParamFir));
    params_nack_fir.Add(FeedbackParam(cricket::kRtcpFbParamNack));
    FeedbackParams params_nack;
    params_nack.Add(FeedbackParam(cricket::kRtcpFbParamNack));

    std::vector<cricket::AudioCodec>
        audio_codecs(kAudioCodecs, kAudioCodecs + ARRAY_SIZE(kAudioCodecs));
    SetCodecFeedbackParams(&audio_codecs, params_nack);
    fme_->SetAudioCodecs(audio_codecs);
    std::vector<cricket::VideoCodec>
        video_codecs(kVideoCodecs, kVideoCodecs + ARRAY_SIZE(kVideoCodecs));
    SetCodecFeedbackParams(&video_codecs, params_nack_fir);
    fme_->SetVideoCodecs(video_codecs);
    std::vector<cricket::DataCodec>
        data_codecs(kDataCodecs, kDataCodecs + ARRAY_SIZE(kDataCodecs));
    SetCodecFeedbackParams(&data_codecs, params_nack);
    fdme_->SetDataCodecs(data_codecs);

    client_ = new cricket::MediaSessionClient(
        buzz::Jid("user@domain.com/resource"), sm_,
        fme_, fdme_, new cricket::FakeDeviceManager());
    client_->session_manager()->SignalOutgoingMessage.connect(
        this, &MediaSessionClientTest::OnSendStanza);
    client_->session_manager()->SignalSessionCreate.connect(
        this, &MediaSessionClientTest::OnSessionCreate);
    client_->SignalCallCreate.connect(
        this, &MediaSessionClientTest::OnCallCreate);
    client_->SignalCallDestroy.connect(
        this, &MediaSessionClientTest::OnCallDestroy);

    call_ = NULL;
    parser_ = parser;
    initial_protocol_ = initial_protocol;
    expect_incoming_crypto_ = false;
    expect_outgoing_crypto_ = false;
    expected_video_bandwidth_ = cricket::kAutoBandwidth;
    expected_video_rtcp_mux_ = false;
  }

  ~MediaSessionClientTest() {
    delete client_;
    delete sm_;
    delete pa_;
    delete nm_;
    delete parser_;
    ClearStanzas();
  }

  buzz::XmlElement* ActionFromStanza(buzz::XmlElement* stanza) {
    return parser_->ActionFromStanza(stanza);
  }

  buzz::XmlElement* ContentFromAction(buzz::XmlElement* action) {
    return parser_->ContentFromAction(action);
  }

  buzz::XmlElement* PayloadTypeFromContent(buzz::XmlElement* payload) {
    return parser_->PayloadTypeFromContent(payload);
  }

  buzz::XmlElement* NextFromPayloadType(buzz::XmlElement* payload_type) {
    return parser_->NextFromPayloadType(payload_type);
  }

  buzz::XmlElement* EncryptionFromContent(buzz::XmlElement* content) {
    return parser_->EncryptionFromContent(content);
  }

  buzz::XmlElement* NextFromEncryption(buzz::XmlElement* encryption) {
    return parser_->NextFromEncryption(encryption);
  }

  cricket::AudioCodec AudioCodecFromPayloadType(
      const buzz::XmlElement* payload_type) {
    return parser_->AudioCodecFromPayloadType(payload_type);
  }

  cricket::VideoCodec VideoCodecFromPayloadType(
      const buzz::XmlElement* payload_type) {
    return parser_->VideoCodecFromPayloadType(payload_type);
  }

  cricket::DataCodec DataCodecFromPayloadType(
      const buzz::XmlElement* payload_type) {
    return parser_->DataCodecFromPayloadType(payload_type);
  }

  const AudioContentDescription* GetFirstAudioContentDescription(
      const cricket::SessionDescription* sdesc) {
    const cricket::ContentInfo* content =
        cricket::GetFirstAudioContent(sdesc);
    if (content == NULL)
      return NULL;
    return static_cast<const AudioContentDescription*>(content->description);
  }

  const cricket::VideoContentDescription* GetFirstVideoContentDescription(
      const cricket::SessionDescription* sdesc) {
    const cricket::ContentInfo* content =
        cricket::GetFirstVideoContent(sdesc);
    if (content == NULL)
      return NULL;
    return static_cast<const cricket::VideoContentDescription*>(
        content->description);
  }

  void CheckCryptoFromGoodIncomingInitiate(const cricket::Session* session) {
    ASSERT_TRUE(session != NULL);
    const AudioContentDescription* content =
        GetFirstAudioContentDescription(session->remote_description());
    ASSERT_TRUE(content != NULL);
    ASSERT_EQ(2U, content->cryptos().size());
    ASSERT_EQ(145, content->cryptos()[0].tag);
    ASSERT_EQ("AES_CM_128_HMAC_SHA1_32", content->cryptos()[0].cipher_suite);
    ASSERT_EQ("inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9",
              content->cryptos()[0].key_params);
    ASSERT_EQ(51, content->cryptos()[1].tag);
    ASSERT_EQ("AES_CM_128_HMAC_SHA1_80", content->cryptos()[1].cipher_suite);
    ASSERT_EQ("inline:J4lfdUL8W1F7TNJKcbuygaQuA429SJy2e9JctPUy",
              content->cryptos()[1].key_params);
  }

  void CheckCryptoForGoodOutgoingAccept(const cricket::Session* session) {
    const AudioContentDescription* content =
        GetFirstAudioContentDescription(session->local_description());
    ASSERT_EQ(1U, content->cryptos().size());
    ASSERT_EQ(145, content->cryptos()[0].tag);
    ASSERT_EQ("AES_CM_128_HMAC_SHA1_32", content->cryptos()[0].cipher_suite);
    ASSERT_EQ(47U, content->cryptos()[0].key_params.size());
  }

  void CheckBadCryptoFromIncomingInitiate(const cricket::Session* session) {
    const AudioContentDescription* content =
        GetFirstAudioContentDescription(session->remote_description());
    ASSERT_EQ(1U, content->cryptos().size());
    ASSERT_EQ(145, content->cryptos()[0].tag);
    ASSERT_EQ("NOT_SUPPORTED", content->cryptos()[0].cipher_suite);
    ASSERT_EQ("inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9",
              content->cryptos()[0].key_params);
  }

  void CheckNoCryptoForOutgoingAccept(const cricket::Session* session) {
    const AudioContentDescription* content =
        GetFirstAudioContentDescription(session->local_description());
    ASSERT_TRUE(content->cryptos().empty());
  }

  void CheckRtcpFb(const cricket::SessionDescription* sdesc) {
    VerifyCodecFbParams<AudioCodec>(expected_audio_fb_params_,
                                    GetFirstAudioContentDescription(sdesc));

    VerifyCodecFbParams<VideoCodec>(expected_video_fb_params_,
                                    GetFirstVideoContentDescription(sdesc));

    VerifyCodecFbParams<DataCodec>(expected_data_fb_params_,
                                   GetFirstDataContentDescription(sdesc));
  }

  void CheckVideoBandwidth(int expected_bandwidth,
                           const cricket::SessionDescription* sdesc) {
    const cricket::VideoContentDescription* video =
        GetFirstVideoContentDescription(sdesc);
    if (video != NULL) {
      ASSERT_EQ(expected_bandwidth, video->bandwidth());
    }
  }

  void CheckVideoRtcpMux(bool expected_video_rtcp_mux,
                         const cricket::SessionDescription* sdesc) {
    const cricket::VideoContentDescription* video =
        GetFirstVideoContentDescription(sdesc);
    if (video != NULL) {
      ASSERT_EQ(expected_video_rtcp_mux, video->rtcp_mux());
    }
  }

  virtual void CheckRtpDataContent(buzz::XmlElement* content) {
    if (initial_protocol_) {
      // Gingle can not write out data content.
      return;
    }

    buzz::XmlElement* e = PayloadTypeFromContent(content);
    ASSERT_TRUE(e != NULL);
    cricket::DataCodec codec = DataCodecFromPayloadType(e);
    EXPECT_EQ(127, codec.id);
    EXPECT_EQ("google-data", codec.name);
    EXPECT_EQ(expected_data_fb_params_, codec.feedback_params);

    CheckDataRtcpMux(true, call_->sessions()[0]->local_description());
    CheckDataRtcpMux(true, call_->sessions()[0]->remote_description());
    if (expect_outgoing_crypto_) {
      content = parser_->NextContent(content);
      buzz::XmlElement* encryption = EncryptionFromContent(content);
      ASSERT_TRUE(encryption != NULL);
      // TODO(pthatcher): Check encryption parameters?
    }
  }

  virtual void CheckSctpDataContent(buzz::XmlElement* content) {
    if (initial_protocol_) {
      // Gingle can not write out data content.
      return;
    }

    buzz::XmlElement* payload_type = PayloadTypeFromContent(content);
    ASSERT_TRUE(payload_type == NULL);
    buzz::XmlElement* encryption = EncryptionFromContent(content);
    ASSERT_TRUE(encryption == NULL);
    // TODO(pthatcher): Check for <streams>.
  }

  void CheckDataRtcpMux(bool expected_data_rtcp_mux,
                        const cricket::SessionDescription* sdesc) {
    const cricket::DataContentDescription* data =
        GetFirstDataContentDescription(sdesc);
    if (data != NULL) {
      ASSERT_EQ(expected_data_rtcp_mux, data->rtcp_mux());
    }
  }

  void CheckAudioSsrcForIncomingAccept(const cricket::Session* session) {
    const AudioContentDescription* audio =
        GetFirstAudioContentDescription(session->remote_description());
    ASSERT_TRUE(audio != NULL);
    ASSERT_EQ(kAudioSsrc, audio->first_ssrc());
  }

  void CheckVideoSsrcForIncomingAccept(const cricket::Session* session) {
    const cricket::VideoContentDescription* video =
        GetFirstVideoContentDescription(session->remote_description());
    ASSERT_TRUE(video != NULL);
    ASSERT_EQ(kVideoSsrc, video->first_ssrc());
  }

  void CheckDataSsrcForIncomingAccept(const cricket::Session* session) {
    const cricket::DataContentDescription* data =
        GetFirstDataContentDescription(session->remote_description());
    ASSERT_TRUE(data != NULL);
    ASSERT_EQ(kDataSsrc, data->first_ssrc());
  }

  void TestGoodIncomingInitiate(const std::string& initiate_string,
                                const cricket::CallOptions& options,
                                buzz::XmlElement** element) {
    *element = NULL;

    rtc::scoped_ptr<buzz::XmlElement> el(
        buzz::XmlElement::ForStr(initiate_string));
    client_->session_manager()->OnIncomingMessage(el.get());
    ASSERT_TRUE(call_ != NULL);
    ASSERT_TRUE(call_->sessions()[0] != NULL);
    ASSERT_EQ(cricket::Session::STATE_RECEIVEDINITIATE,
              call_->sessions()[0]->state());
    ASSERT_EQ(1U, stanzas_.size());
    ASSERT_TRUE(buzz::QN_IQ == stanzas_[0]->Name());
    ASSERT_TRUE(stanzas_[0]->HasAttr(buzz::QN_TYPE));
    ASSERT_EQ(std::string(buzz::STR_RESULT), stanzas_[0]->Attr(buzz::QN_TYPE));
    ClearStanzas();
    CheckVideoBandwidth(expected_video_bandwidth_,
                        call_->sessions()[0]->remote_description());
    CheckVideoRtcpMux(expected_video_rtcp_mux_,
                      call_->sessions()[0]->remote_description());
    CheckRtcpFb(call_->sessions()[0]->remote_description());
    if (expect_incoming_crypto_) {
      CheckCryptoFromGoodIncomingInitiate(call_->sessions()[0]);
    }

    // TODO(pthatcher): Add tests for sending <bandwidth> in accept.
    call_->AcceptSession(call_->sessions()[0], options);
    ASSERT_EQ(cricket::Session::STATE_SENTACCEPT,
              call_->sessions()[0]->state());
    ASSERT_EQ(1U, stanzas_.size());
    ASSERT_TRUE(buzz::QN_IQ == stanzas_[0]->Name());
    ASSERT_TRUE(stanzas_[0]->HasAttr(buzz::QN_TYPE));
    ASSERT_EQ(std::string(buzz::STR_SET), stanzas_[0]->Attr(buzz::QN_TYPE));

    buzz::XmlElement* e = ActionFromStanza(stanzas_[0]);
    ASSERT_TRUE(e != NULL);
    ASSERT_TRUE(ContentFromAction(e) != NULL);
    *element = CopyElement(ContentFromAction(e));
    ASSERT_TRUE(*element != NULL);
    ClearStanzas();
    if (expect_outgoing_crypto_) {
      CheckCryptoForGoodOutgoingAccept(call_->sessions()[0]);
    }

    if (options.data_channel_type == cricket::DCT_RTP) {
      CheckDataRtcpMux(true, call_->sessions()[0]->local_description());
      CheckDataRtcpMux(true, call_->sessions()[0]->remote_description());
      // TODO(pthatcher): Check rtcpmux and crypto?
    }

    call_->Terminate();
    ASSERT_EQ(cricket::Session::STATE_SENTTERMINATE,
              call_->sessions()[0]->state());
    ASSERT_EQ(1U, stanzas_.size());
    ASSERT_TRUE(buzz::QN_IQ == stanzas_[0]->Name());
    ASSERT_TRUE(stanzas_[0]->HasAttr(buzz::QN_TYPE));
    ASSERT_EQ(std::string(buzz::STR_SET), stanzas_[0]->Attr(buzz::QN_TYPE));
    e = ActionFromStanza(stanzas_[0]);
    ASSERT_TRUE(e != NULL);
    ASSERT_TRUE(parser_->ActionIsTerminate(e));
    ClearStanzas();
  }

  void TestRejectOffer(const std::string &initiate_string,
                       const cricket::CallOptions& options,
                       buzz::XmlElement** element) {
    *element = NULL;

    rtc::scoped_ptr<buzz::XmlElement> el(
        buzz::XmlElement::ForStr(initiate_string));
    client_->session_manager()->OnIncomingMessage(el.get());
    ASSERT_TRUE(call_ != NULL);
    ASSERT_TRUE(call_->sessions()[0] != NULL);
    ASSERT_EQ(cricket::Session::STATE_RECEIVEDINITIATE,
              call_->sessions()[0]->state());
    ASSERT_EQ(1U, stanzas_.size());
    ASSERT_TRUE(buzz::QN_IQ == stanzas_[0]->Name());
    ASSERT_TRUE(stanzas_[0]->HasAttr(buzz::QN_TYPE));
    ASSERT_EQ(std::string(buzz::STR_RESULT), stanzas_[0]->Attr(buzz::QN_TYPE));
    ClearStanzas();

    call_->AcceptSession(call_->sessions()[0], options);
    ASSERT_EQ(cricket::Session::STATE_SENTACCEPT,
              call_->sessions()[0]->state());
    ASSERT_EQ(1U, stanzas_.size());
    ASSERT_TRUE(buzz::QN_IQ == stanzas_[0]->Name());
    ASSERT_TRUE(stanzas_[0]->HasAttr(buzz::QN_TYPE));
    ASSERT_EQ(std::string(buzz::STR_SET), stanzas_[0]->Attr(buzz::QN_TYPE));

    buzz::XmlElement* e = ActionFromStanza(stanzas_[0]);
    ASSERT_TRUE(e != NULL);
    ASSERT_TRUE(ContentFromAction(e) != NULL);
    *element = CopyElement(ContentFromAction(e));
    ASSERT_TRUE(*element != NULL);
    ClearStanzas();

    buzz::XmlElement* content = *element;
    // The NextContent method actually returns the second content. So we
    // can't handle the case when audio, video and data are all enabled. But
    // since we are testing rejection, it won't be the case.
    if (options.has_audio) {
      ASSERT_TRUE(content != NULL);
      ASSERT_EQ("test audio", content->Attr(buzz::QName("", "name")));
      content = parser_->NextContent(content);
    }

    if (options.has_video) {
      ASSERT_TRUE(content != NULL);
      ASSERT_EQ("test video", content->Attr(buzz::QName("", "name")));
      content = parser_->NextContent(content);
    }

    if (options.has_data()) {
      ASSERT_TRUE(content != NULL);
      ASSERT_EQ("test data", content->Attr(buzz::QName("", "name")));
      content = parser_->NextContent(content);
    }

    call_->Terminate();
    ASSERT_EQ(cricket::Session::STATE_SENTTERMINATE,
              call_->sessions()[0]->state());
    ASSERT_EQ(1U, stanzas_.size());
    ASSERT_TRUE(buzz::QN_IQ == stanzas_[0]->Name());
    ASSERT_TRUE(stanzas_[0]->HasAttr(buzz::QN_TYPE));
    ASSERT_EQ(std::string(buzz::STR_SET), stanzas_[0]->Attr(buzz::QN_TYPE));
    e = ActionFromStanza(stanzas_[0]);
    ASSERT_TRUE(e != NULL);
    ASSERT_TRUE(parser_->ActionIsTerminate(e));
    ClearStanzas();
  }

  void TestBadIncomingInitiate(const std::string& initiate_string) {
    rtc::scoped_ptr<buzz::XmlElement> el(
        buzz::XmlElement::ForStr(initiate_string));
    client_->session_manager()->OnIncomingMessage(el.get());
    ASSERT_TRUE(call_ != NULL);
    ASSERT_TRUE(call_->sessions()[0] != NULL);
    ASSERT_EQ(cricket::Session::STATE_SENTREJECT,
              call_->sessions()[0]->state());
    ASSERT_EQ(2U, stanzas_.size());
    ASSERT_TRUE(buzz::QN_IQ == stanzas_[0]->Name());
    ASSERT_TRUE(stanzas_[1]->HasAttr(buzz::QN_TYPE));
    ASSERT_EQ(std::string(buzz::STR_RESULT), stanzas_[1]->Attr(buzz::QN_TYPE));
    ClearStanzas();
  }

  void VerifyAudioCodec(const AudioCodec& codec, int id,
                        const std::string& name, int clockrate,
                        int bitrate, int channels) {
    ASSERT_EQ(id, codec.id);
    ASSERT_EQ(name, codec.name);
    ASSERT_EQ(clockrate, codec.clockrate);
    ASSERT_EQ(bitrate, codec.bitrate);
    ASSERT_EQ(channels, codec.channels);
    ASSERT_EQ(expected_audio_fb_params_, codec.feedback_params);
  }

  void TestGoodOutgoingInitiate(const cricket::CallOptions& options) {
    if (initial_protocol_ == cricket::PROTOCOL_JINGLE) {
      // rtcp fb is only implemented for jingle.
      ExpectRtcpFb();
    }

    client_->CreateCall();
    ASSERT_TRUE(call_ != NULL);
    call_->InitiateSession(buzz::Jid("me@mydomain.com"),
                           buzz::Jid("me@mydomain.com"), options);
    ASSERT_TRUE(call_->sessions()[0] != NULL);
    ASSERT_EQ(cricket::Session::STATE_SENTINITIATE,
              call_->sessions()[0]->state());
    ASSERT_EQ(1U, stanzas_.size());
    ASSERT_TRUE(buzz::QN_IQ == stanzas_[0]->Name());
    ASSERT_TRUE(stanzas_[0]->HasAttr(buzz::QN_TYPE));
    ASSERT_EQ(std::string(buzz::STR_SET), stanzas_[0]->Attr(buzz::QN_TYPE));
    buzz::XmlElement* action = ActionFromStanza(stanzas_[0]);
    ASSERT_TRUE(action != NULL);
    buzz::XmlElement* content = ContentFromAction(action);
    ASSERT_TRUE(content != NULL);

    buzz::XmlElement* e = PayloadTypeFromContent(content);
    ASSERT_TRUE(e != NULL);
    cricket::AudioCodec codec = AudioCodecFromPayloadType(e);
    VerifyAudioCodec(codec, 103, "ISAC", 16000, 0, 1);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    VerifyAudioCodec(codec, 104, "ISAC", 32000, 0, 1);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    VerifyAudioCodec(codec, 119, "ISACLC", 16000, 40000, 1);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    VerifyAudioCodec(codec, 99, "speex", 16000, 22000, 1);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    VerifyAudioCodec(codec, 97, "IPCMWB", 16000, 80000, 1);

     e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    VerifyAudioCodec(codec, 9, "G722", 16000, 64000, 1);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    VerifyAudioCodec(codec, 102, "iLBC", 8000, 13300, 1);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    VerifyAudioCodec(codec, 98, "speex", 8000, 11000, 1);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    VerifyAudioCodec(codec, 3, "GSM", 8000, 13000, 1);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    VerifyAudioCodec(codec, 100, "EG711U", 8000, 64000, 1);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    VerifyAudioCodec(codec, 101, "EG711A", 8000, 64000, 1);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    VerifyAudioCodec(codec, 0, "PCMU", 8000, 64000, 1);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    VerifyAudioCodec(codec, 8, "PCMA", 8000, 64000, 1);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    VerifyAudioCodec(codec, 126, "CN", 32000, 0, 1);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    VerifyAudioCodec(codec, 105, "CN", 16000, 0, 1);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    VerifyAudioCodec(codec, 13, "CN", 8000, 0, 1);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    VerifyAudioCodec(codec, 117, "red", 8000, 0, 1);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    VerifyAudioCodec(codec, 106, "telephone-event", 8000, 0, 1);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e == NULL);

    if (expect_outgoing_crypto_) {
      buzz::XmlElement* encryption = EncryptionFromContent(content);
      ASSERT_TRUE(encryption != NULL);

      if (client_->secure() == cricket::SEC_REQUIRED) {
        ASSERT_TRUE(cricket::GetXmlAttr(
            encryption, cricket::QN_ENCRYPTION_REQUIRED, false));
      }

      if (content->Name().Namespace() == cricket::NS_GINGLE_AUDIO) {
        e = encryption->FirstNamed(cricket::QN_GINGLE_AUDIO_CRYPTO_USAGE);
        ASSERT_TRUE(e != NULL);
        ASSERT_TRUE(
            e->NextNamed(cricket::QN_GINGLE_AUDIO_CRYPTO_USAGE) == NULL);
        ASSERT_TRUE(
            e->FirstNamed(cricket::QN_GINGLE_VIDEO_CRYPTO_USAGE) == NULL);
      }

      e = encryption->FirstNamed(cricket::QN_CRYPTO);
      ASSERT_TRUE(e != NULL);
      ASSERT_EQ("0", e->Attr(cricket::QN_CRYPTO_TAG));
      ASSERT_EQ("AES_CM_128_HMAC_SHA1_32", e->Attr(cricket::QN_CRYPTO_SUITE));
      std::string key_0 = e->Attr(cricket::QN_CRYPTO_KEY_PARAMS);
      ASSERT_EQ(47U, key_0.length());
      ASSERT_EQ("inline:", key_0.substr(0, 7));

      e = e->NextNamed(cricket::QN_CRYPTO);
      ASSERT_TRUE(e != NULL);
      ASSERT_EQ("1", e->Attr(cricket::QN_CRYPTO_TAG));
      ASSERT_EQ("AES_CM_128_HMAC_SHA1_80", e->Attr(cricket::QN_CRYPTO_SUITE));
      std::string key_1 = e->Attr(cricket::QN_CRYPTO_KEY_PARAMS);
      ASSERT_EQ(47U, key_1.length());
      ASSERT_EQ("inline:", key_1.substr(0, 7));
      ASSERT_NE(key_0, key_1);

      encryption = NextFromEncryption(encryption);
      ASSERT_TRUE(encryption == NULL);
    }

    if (options.has_video) {
      CheckVideoBandwidth(options.video_bandwidth,
                          call_->sessions()[0]->local_description());
      CheckVideoRtcpMux(expected_video_rtcp_mux_,
                        call_->sessions()[0]->remote_description());
      content = parser_->NextContent(content);
      const buzz::XmlElement* bandwidth =
          parser_->BandwidthFromContent(content);
      if (options.video_bandwidth == cricket::kAutoBandwidth) {
        ASSERT_TRUE(bandwidth == NULL);
      } else {
        ASSERT_TRUE(bandwidth != NULL);
        ASSERT_EQ("AS", bandwidth->Attr(buzz::QName("", "type")));
        ASSERT_EQ(rtc::ToString(options.video_bandwidth / 1000),
                  bandwidth->BodyText());
      }

      buzz::XmlElement* e = PayloadTypeFromContent(content);
      ASSERT_TRUE(e != NULL);
      VideoCodec codec = VideoCodecFromPayloadType(e);
      VideoCodec expected_codec = kVideoCodecs[0];
      expected_codec.preference = codec.preference;
      expected_codec.feedback_params = expected_video_fb_params_;
      EXPECT_EQ(expected_codec, codec);
    }

    if (options.data_channel_type == cricket::DCT_RTP) {
      content = parser_->NextContent(content);
      CheckRtpDataContent(content);
    }

    if (options.data_channel_type == cricket::DCT_SCTP) {
      content = parser_->NextContent(content);
      CheckSctpDataContent(content);
    }

    ClearStanzas();
  }

  void TestHasAllSupportedAudioCodecs(buzz::XmlElement* e) {
    ASSERT_TRUE(e != NULL);

    e = PayloadTypeFromContent(e);
    ASSERT_TRUE(e != NULL);
    cricket::AudioCodec codec = AudioCodecFromPayloadType(e);
    ASSERT_EQ(103, codec.id);
    ASSERT_EQ("ISAC", codec.name);
    ASSERT_EQ(16000, codec.clockrate);
    ASSERT_EQ(1, codec.channels);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    ASSERT_EQ(104, codec.id);
    ASSERT_EQ("ISAC", codec.name);
    ASSERT_EQ(32000, codec.clockrate);
    ASSERT_EQ(1, codec.channels);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    ASSERT_EQ(119, codec.id);
    ASSERT_EQ("ISACLC", codec.name);
    ASSERT_EQ(16000, codec.clockrate);
    ASSERT_EQ(40000, codec.bitrate);
    ASSERT_EQ(1, codec.channels);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    ASSERT_EQ(99, codec.id);
    ASSERT_EQ("speex", codec.name);
    ASSERT_EQ(16000, codec.clockrate);
    ASSERT_EQ(22000, codec.bitrate);
    ASSERT_EQ(1, codec.channels);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    ASSERT_EQ(97, codec.id);
    ASSERT_EQ("IPCMWB", codec.name);
    ASSERT_EQ(16000, codec.clockrate);
    ASSERT_EQ(80000, codec.bitrate);
    ASSERT_EQ(1, codec.channels);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    ASSERT_EQ(9, codec.id);
    ASSERT_EQ("G722", codec.name);
    ASSERT_EQ(16000, codec.clockrate);
    ASSERT_EQ(64000, codec.bitrate);
    ASSERT_EQ(1, codec.channels);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    ASSERT_EQ(102, codec.id);
    ASSERT_EQ("iLBC", codec.name);
    ASSERT_EQ(8000, codec.clockrate);
    ASSERT_EQ(13300, codec.bitrate);
    ASSERT_EQ(1, codec.channels);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    ASSERT_EQ(98, codec.id);
    ASSERT_EQ("speex", codec.name);
    ASSERT_EQ(8000, codec.clockrate);
    ASSERT_EQ(11000, codec.bitrate);
    ASSERT_EQ(1, codec.channels);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    ASSERT_EQ(3, codec.id);
    ASSERT_EQ("GSM", codec.name);
    ASSERT_EQ(8000, codec.clockrate);
    ASSERT_EQ(13000, codec.bitrate);
    ASSERT_EQ(1, codec.channels);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    ASSERT_EQ(100, codec.id);
    ASSERT_EQ("EG711U", codec.name);
    ASSERT_EQ(8000, codec.clockrate);
    ASSERT_EQ(64000, codec.bitrate);
    ASSERT_EQ(1, codec.channels);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    ASSERT_EQ(101, codec.id);
    ASSERT_EQ("EG711A", codec.name);
    ASSERT_EQ(8000, codec.clockrate);
    ASSERT_EQ(64000, codec.bitrate);
    ASSERT_EQ(1, codec.channels);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    ASSERT_EQ(0, codec.id);
    ASSERT_EQ("PCMU", codec.name);
    ASSERT_EQ(8000, codec.clockrate);
    ASSERT_EQ(64000, codec.bitrate);
    ASSERT_EQ(1, codec.channels);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    ASSERT_EQ(8, codec.id);
    ASSERT_EQ("PCMA", codec.name);
    ASSERT_EQ(8000, codec.clockrate);
    ASSERT_EQ(64000, codec.bitrate);
    ASSERT_EQ(1, codec.channels);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    ASSERT_EQ(126, codec.id);
    ASSERT_EQ("CN", codec.name);
    ASSERT_EQ(32000, codec.clockrate);
    ASSERT_EQ(1, codec.channels);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    ASSERT_EQ(105, codec.id);
    ASSERT_EQ("CN", codec.name);
    ASSERT_EQ(16000, codec.clockrate);
    ASSERT_EQ(1, codec.channels);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    ASSERT_EQ(13, codec.id);
    ASSERT_EQ("CN", codec.name);
    ASSERT_EQ(8000, codec.clockrate);
    ASSERT_EQ(1, codec.channels);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    ASSERT_EQ(117, codec.id);
    ASSERT_EQ("red", codec.name);
    ASSERT_EQ(8000, codec.clockrate);
    ASSERT_EQ(1, codec.channels);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    ASSERT_EQ(106, codec.id);
    ASSERT_EQ("telephone-event", codec.name);
    ASSERT_EQ(8000, codec.clockrate);
    ASSERT_EQ(1, codec.channels);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e == NULL);
  }

  void TestCodecsOfVideoInitiate(buzz::XmlElement* content) {
    ASSERT_TRUE(content != NULL);
    buzz::XmlElement* payload_type = PayloadTypeFromContent(content);
    ASSERT_TRUE(payload_type != NULL);
    cricket::AudioCodec codec = AudioCodecFromPayloadType(payload_type);
    ASSERT_EQ(103, codec.id);
    ASSERT_EQ("ISAC", codec.name);
    ASSERT_EQ(16000, codec.clockrate);
    ASSERT_EQ(1, codec.channels);

    content = parser_->NextContent(content);
    ASSERT_TRUE(content != NULL);
    payload_type = PayloadTypeFromContent(content);
    ASSERT_TRUE(payload_type != NULL);
    cricket::VideoCodec vcodec =
        parser_->VideoCodecFromPayloadType(payload_type);
    ASSERT_EQ(99, vcodec.id);
    ASSERT_EQ("H264-SVC", vcodec.name);
    ASSERT_EQ(320, vcodec.width);
    ASSERT_EQ(200, vcodec.height);
    ASSERT_EQ(30, vcodec.framerate);
  }

  void TestHasAudioCodecsFromInitiateSomeUnsupported(buzz::XmlElement* e) {
    ASSERT_TRUE(e != NULL);
    e = PayloadTypeFromContent(e);
    ASSERT_TRUE(e != NULL);

    cricket::AudioCodec codec = AudioCodecFromPayloadType(e);
    ASSERT_EQ(103, codec.id);
    ASSERT_EQ("ISAC", codec.name);
    ASSERT_EQ(16000, codec.clockrate);
    ASSERT_EQ(1, codec.channels);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    ASSERT_EQ(100, codec.id);
    ASSERT_EQ("EG711U", codec.name);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    ASSERT_EQ(101, codec.id);
    ASSERT_EQ("EG711A", codec.name);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    ASSERT_EQ(0, codec.id);
    ASSERT_EQ("PCMU", codec.name);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    ASSERT_EQ(13, codec.id);
    ASSERT_EQ("CN", codec.name);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e == NULL);
  }

  void TestHasAudioCodecsFromInitiateDynamicAudioCodecs(
      buzz::XmlElement* e) {
    ASSERT_TRUE(e != NULL);
    e = PayloadTypeFromContent(e);
    ASSERT_TRUE(e != NULL);

    cricket::AudioCodec codec = AudioCodecFromPayloadType(e);
    ASSERT_EQ(123, codec.id);
    ASSERT_EQ(16000, codec.clockrate);
    ASSERT_EQ(1, codec.channels);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e == NULL);
  }

  void TestHasDefaultAudioCodecs(buzz::XmlElement* e) {
    ASSERT_TRUE(e != NULL);
    e = PayloadTypeFromContent(e);
    ASSERT_TRUE(e != NULL);

    cricket::AudioCodec codec = AudioCodecFromPayloadType(e);
    ASSERT_EQ(103, codec.id);
    ASSERT_EQ("ISAC", codec.name);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    ASSERT_EQ(0, codec.id);
    ASSERT_EQ("PCMU", codec.name);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e == NULL);
  }

  void TestHasAudioCodecsFromInitiateStaticAudioCodecs(
      buzz::XmlElement* e) {
    ASSERT_TRUE(e != NULL);
    e = PayloadTypeFromContent(e);
    ASSERT_TRUE(e != NULL);

    cricket::AudioCodec codec = AudioCodecFromPayloadType(e);
    ASSERT_EQ(3, codec.id);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    ASSERT_EQ(0, codec.id);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e != NULL);
    codec = AudioCodecFromPayloadType(e);
    ASSERT_EQ(8, codec.id);

    e = NextFromPayloadType(e);
    ASSERT_TRUE(e == NULL);
  }

  void TestGingleInitiateWithUnsupportedCrypto(
      const std::string &initiate_string,
      buzz::XmlElement** element) {
    *element = NULL;

    rtc::scoped_ptr<buzz::XmlElement> el(
        buzz::XmlElement::ForStr(initiate_string));
    client_->session_manager()->OnIncomingMessage(el.get());

    ASSERT_EQ(cricket::Session::STATE_RECEIVEDINITIATE,
              call_->sessions()[0]->state());
    ClearStanzas();
    CheckBadCryptoFromIncomingInitiate(call_->sessions()[0]);

    call_->AcceptSession(call_->sessions()[0], cricket::CallOptions());
    ClearStanzas();
    CheckNoCryptoForOutgoingAccept(call_->sessions()[0]);

    call_->Terminate();
    ASSERT_EQ(cricket::Session::STATE_SENTTERMINATE,
              call_->sessions()[0]->state());
    ClearStanzas();
  }

  void TestIncomingAcceptWithSsrcs(
      const std::string& accept_string,
      cricket::CallOptions& options) {
    client_->CreateCall();
    ASSERT_TRUE(call_ != NULL);

    call_->InitiateSession(buzz::Jid("me@mydomain.com"),
                           buzz::Jid("me@mydomain.com"), options);
    ASSERT_TRUE(call_->sessions()[0] != NULL);
    ASSERT_EQ(cricket::Session::STATE_SENTINITIATE,
              call_->sessions()[0]->state());
    ASSERT_EQ(1U, stanzas_.size());
    ASSERT_TRUE(buzz::QN_IQ == stanzas_[0]->Name());
    ASSERT_TRUE(stanzas_[0]->HasAttr(buzz::QN_TYPE));
    ASSERT_EQ(std::string(buzz::STR_SET), stanzas_[0]->Attr(buzz::QN_TYPE));
    buzz::XmlElement* action = ActionFromStanza(stanzas_[0]);
    ASSERT_TRUE(action != NULL);
    buzz::XmlElement* content = ContentFromAction(action);
    ASSERT_TRUE(content != NULL);
    if (initial_protocol_ == cricket::PROTOCOL_JINGLE) {
      buzz::XmlElement* content_desc =
          content->FirstNamed(cricket::QN_JINGLE_RTP_CONTENT);
      ASSERT_TRUE(content_desc != NULL);
      ASSERT_EQ("", content_desc->Attr(cricket::QN_SSRC));
    }
    ClearStanzas();

    // We need to insert the session ID into the session accept message.
    rtc::scoped_ptr<buzz::XmlElement> el(
        buzz::XmlElement::ForStr(accept_string));
    const std::string sid = call_->sessions()[0]->id();
    if (initial_protocol_ == cricket::PROTOCOL_JINGLE) {
      buzz::XmlElement* jingle = el->FirstNamed(cricket::QN_JINGLE);
      jingle->SetAttr(cricket::QN_SID, sid);
    } else {
      buzz::XmlElement* session = el->FirstNamed(cricket::QN_GINGLE_SESSION);
      session->SetAttr(cricket::QN_ID, sid);
    }

    client_->session_manager()->OnIncomingMessage(el.get());

    ASSERT_EQ(cricket::Session::STATE_RECEIVEDACCEPT,
              call_->sessions()[0]->state());
    ASSERT_EQ(1U, stanzas_.size());
    ASSERT_TRUE(buzz::QN_IQ == stanzas_[0]->Name());
    ASSERT_TRUE(stanzas_[0]->HasAttr(buzz::QN_TYPE));
    ASSERT_EQ(std::string(buzz::STR_RESULT), stanzas_[0]->Attr(buzz::QN_TYPE));
    ClearStanzas();

    CheckAudioSsrcForIncomingAccept(call_->sessions()[0]);
    CheckVideoSsrcForIncomingAccept(call_->sessions()[0]);
    if (options.data_channel_type == cricket::DCT_RTP) {
      CheckDataSsrcForIncomingAccept(call_->sessions()[0]);
    }
    // TODO(pthatcher): Check kDataSid if DCT_SCTP.
    // const uint32 kDataSid = 0;
  }

  size_t ClearStanzas() {
    size_t size = stanzas_.size();
    for (size_t i = 0; i < size; i++) {
      delete stanzas_[i];
    }
    stanzas_.clear();
    return size;
  }

  buzz::XmlElement* SetJingleSid(buzz::XmlElement* stanza) {
    buzz::XmlElement* jingle =
        stanza->FirstNamed(cricket::QN_JINGLE);
    jingle->SetAttr(cricket::QN_SID, call_->sessions()[0]->id());
    return stanza;
  }

  void TestSendVideoStreamUpdate() {
    cricket::CallOptions options = VideoCallOptions();
    options.is_muc = true;

    client_->CreateCall();
    call_->InitiateSession(buzz::Jid("me@mydomain.com"),
                           buzz::Jid("me@mydomain.com"), options);
    ClearStanzas();

    cricket::StreamParams stream;
    stream.id = "test-stream";
    stream.ssrcs.push_back(1001);
    rtc::scoped_ptr<buzz::XmlElement> expected_stream_add(
        buzz::XmlElement::ForStr(
            JingleOutboundStreamAdd(
                call_->sessions()[0]->id(),
                "video", stream.id, "1001")));
    rtc::scoped_ptr<buzz::XmlElement> expected_stream_remove(
        buzz::XmlElement::ForStr(
            JingleOutboundStreamRemove(
                call_->sessions()[0]->id(),
                "video", stream.id)));

    call_->SendVideoStreamUpdate(call_->sessions()[0],
                                 call_->CreateVideoStreamUpdate(stream));
    ASSERT_EQ(1U, stanzas_.size());
    EXPECT_EQ(expected_stream_add->Str(), stanzas_[0]->Str());
    ClearStanzas();

    stream.ssrcs.clear();
    call_->SendVideoStreamUpdate(call_->sessions()[0],
                                 call_->CreateVideoStreamUpdate(stream));
    ASSERT_EQ(1U, stanzas_.size());
    EXPECT_EQ(expected_stream_remove->Str(), stanzas_[0]->Str());
    ClearStanzas();
  }

  void TestStreamsUpdateAndViewRequests() {
    cricket::CallOptions options = VideoCallOptions();
    options.is_muc = true;

    client_->CreateCall();
    call_->InitiateSession(buzz::Jid("me@mydomain.com"),
                           buzz::Jid("me@mydomain.com"), options);
    ASSERT_EQ(1U, ClearStanzas());
    ASSERT_EQ(0U, last_streams_added_.audio().size());
    ASSERT_EQ(0U, last_streams_added_.video().size());
    ASSERT_EQ(0U, last_streams_removed_.audio().size());
    ASSERT_EQ(0U, last_streams_removed_.video().size());

    rtc::scoped_ptr<buzz::XmlElement> accept_stanza(
        buzz::XmlElement::ForStr(kJingleAcceptWithSsrcs));
    SetJingleSid(accept_stanza.get());
    client_->session_manager()->OnIncomingMessage(accept_stanza.get());
    ASSERT_EQ(cricket::Session::STATE_RECEIVEDACCEPT,
              call_->sessions()[0]->state());
    ASSERT_EQ(1U, stanzas_.size());
    ASSERT_EQ(std::string(buzz::STR_RESULT), stanzas_[0]->Attr(buzz::QN_TYPE));
    ClearStanzas();
    // Need to clear the added streams, because they are populated when
    // receiving an accept message now.
    last_streams_added_.mutable_video()->clear();
    last_streams_added_.mutable_audio()->clear();

    call_->sessions()[0]->SetState(cricket::Session::STATE_INPROGRESS);

    rtc::scoped_ptr<buzz::XmlElement> streams_stanza(
        buzz::XmlElement::ForStr(
            JingleStreamAdd("video", "Bob", "video1", "ABC")));
    SetJingleSid(streams_stanza.get());
    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
    // First one is ignored because of bad syntax.
    ASSERT_EQ(1U, stanzas_.size());
    // TODO(pthatcher): Figure out how to make this an ERROR rather than RESULT.
    ASSERT_EQ(std::string(buzz::STR_ERROR), stanzas_[0]->Attr(buzz::QN_TYPE));
    ClearStanzas();
    ASSERT_EQ(0U, last_streams_added_.audio().size());
    ASSERT_EQ(0U, last_streams_added_.video().size());
    ASSERT_EQ(0U, last_streams_removed_.audio().size());
    ASSERT_EQ(0U, last_streams_removed_.video().size());

    streams_stanza.reset(buzz::XmlElement::ForStr(
        JingleStreamAdd("audio", "Bob", "audio1", "1234")));
    SetJingleSid(streams_stanza.get());
    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
    ASSERT_EQ(1U, last_streams_added_.audio().size());
    ASSERT_EQ("Bob", last_streams_added_.audio()[0].groupid);
    ASSERT_EQ(1U, last_streams_added_.audio()[0].ssrcs.size());
    ASSERT_EQ(1234U, last_streams_added_.audio()[0].first_ssrc());

    // Ignores adds without ssrcs.
    streams_stanza.reset(buzz::XmlElement::ForStr(
        JingleStreamAddWithoutSsrc("audio", "Bob", "audioX")));
    SetJingleSid(streams_stanza.get());
    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
    ASSERT_EQ(1U, last_streams_added_.audio().size());
    ASSERT_EQ(1234U, last_streams_added_.audio()[0].first_ssrc());

    // Ignores stream updates with unknown content names. (Don't terminate).
    streams_stanza.reset(buzz::XmlElement::ForStr(
        JingleStreamAddWithoutSsrc("foo", "Bob", "foo")));
    SetJingleSid(streams_stanza.get());
    client_->session_manager()->OnIncomingMessage(streams_stanza.get());

    streams_stanza.reset(buzz::XmlElement::ForStr(
        JingleStreamAdd("audio", "Joe", "audio1", "2468")));
    SetJingleSid(streams_stanza.get());
    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
    ASSERT_EQ(1U, last_streams_added_.audio().size());
    ASSERT_EQ("Joe", last_streams_added_.audio()[0].groupid);
    ASSERT_EQ(1U, last_streams_added_.audio()[0].ssrcs.size());
    ASSERT_EQ(2468U, last_streams_added_.audio()[0].first_ssrc());

    streams_stanza.reset(buzz::XmlElement::ForStr(
        JingleStreamAdd("video", "Bob", "video1", "5678")));
    SetJingleSid(streams_stanza.get());
    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
    ASSERT_EQ(1U, last_streams_added_.video().size());
    ASSERT_EQ("Bob", last_streams_added_.video()[0].groupid);
    ASSERT_EQ(1U, last_streams_added_.video()[0].ssrcs.size());
    ASSERT_EQ(5678U, last_streams_added_.video()[0].first_ssrc());

    // We're testing that a "duplicate" is effectively ignored.
    last_streams_added_.mutable_video()->clear();
    last_streams_removed_.mutable_video()->clear();
    streams_stanza.reset(buzz::XmlElement::ForStr(
        JingleStreamAdd("video", "Bob", "video1", "5678")));
    SetJingleSid(streams_stanza.get());
    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
    ASSERT_EQ(0U, last_streams_added_.video().size());
    ASSERT_EQ(0U, last_streams_removed_.video().size());

    streams_stanza.reset(buzz::XmlElement::ForStr(
        JingleStreamAdd("video", "Bob", "video2", "5679")));
    SetJingleSid(streams_stanza.get());
    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
    ASSERT_EQ(1U, last_streams_added_.video().size());
    ASSERT_EQ("Bob", last_streams_added_.video()[0].groupid);
    ASSERT_EQ(1U, last_streams_added_.video()[0].ssrcs.size());
    ASSERT_EQ(5679U, last_streams_added_.video()[0].first_ssrc());

    cricket::FakeVoiceMediaChannel* voice_channel = fme_->GetVoiceChannel(0);
    ASSERT_TRUE(voice_channel != NULL);
    ASSERT_TRUE(voice_channel->HasRecvStream(1234U));
    ASSERT_TRUE(voice_channel->HasRecvStream(2468U));
    cricket::FakeVideoMediaChannel* video_channel = fme_->GetVideoChannel(0);
    ASSERT_TRUE(video_channel != NULL);
    ASSERT_TRUE(video_channel->HasRecvStream(5678U));
    ClearStanzas();

    cricket::ViewRequest viewRequest;
    cricket::StaticVideoView staticVideoView(
        cricket::StreamSelector(5678U), 640, 480, 30);
    viewRequest.static_video_views.push_back(staticVideoView);
    rtc::scoped_ptr<buzz::XmlElement> expected_view_elem(
        buzz::XmlElement::ForStr(JingleView("5678", "640", "480", "30")));
    SetJingleSid(expected_view_elem.get());

    ASSERT_TRUE(
        call_->SendViewRequest(call_->sessions()[0], viewRequest));
    ASSERT_EQ(1U, stanzas_.size());
    ASSERT_EQ(expected_view_elem->Str(), stanzas_[0]->Str());
    ClearStanzas();

    streams_stanza.reset(buzz::XmlElement::ForStr(
        JingleStreamRemove("audio", "Bob", "audio1")));
    SetJingleSid(streams_stanza.get());
    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
    ASSERT_EQ(1U, last_streams_removed_.audio().size());
    ASSERT_EQ(1U, last_streams_removed_.audio()[0].ssrcs.size());
    EXPECT_EQ(1234U, last_streams_removed_.audio()[0].first_ssrc());

    streams_stanza.reset(buzz::XmlElement::ForStr(
        JingleStreamRemove("video", "Bob", "video1")));
    SetJingleSid(streams_stanza.get());
    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
    ASSERT_EQ(1U, last_streams_removed_.video().size());
    ASSERT_EQ(1U, last_streams_removed_.video()[0].ssrcs.size());
    EXPECT_EQ(5678U, last_streams_removed_.video()[0].first_ssrc());

    streams_stanza.reset(buzz::XmlElement::ForStr(
        JingleStreamRemove("video", "Bob", "video2")));
    SetJingleSid(streams_stanza.get());
    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
    ASSERT_EQ(1U, last_streams_removed_.video().size());
    ASSERT_EQ(1U, last_streams_removed_.video()[0].ssrcs.size());
    EXPECT_EQ(5679U, last_streams_removed_.video()[0].first_ssrc());

    // Duplicate removal: should be ignored.
    last_streams_removed_.mutable_audio()->clear();
    streams_stanza.reset(buzz::XmlElement::ForStr(
        JingleStreamRemove("audio", "Bob", "audio1")));
    SetJingleSid(streams_stanza.get());
    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
    ASSERT_EQ(0U, last_streams_removed_.audio().size());

    // Duplicate removal: should be ignored.
    last_streams_removed_.mutable_video()->clear();
    streams_stanza.reset(buzz::XmlElement::ForStr(
        JingleStreamRemove("video", "Bob", "video1")));
    SetJingleSid(streams_stanza.get());
    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
    ASSERT_EQ(0U, last_streams_removed_.video().size());

    voice_channel = fme_->GetVoiceChannel(0);
    ASSERT_TRUE(voice_channel != NULL);
    ASSERT_FALSE(voice_channel->HasRecvStream(1234U));
    ASSERT_TRUE(voice_channel->HasRecvStream(2468U));
    video_channel = fme_->GetVideoChannel(0);
    ASSERT_TRUE(video_channel != NULL);
    ASSERT_FALSE(video_channel->HasRecvStream(5678U));

    // Fails because ssrc is now invalid.
    ASSERT_FALSE(
        call_->SendViewRequest(call_->sessions()[0], viewRequest));

    ClearStanzas();
  }

  void MakeSignalingSecure(cricket::SecurePolicy secure) {
    client_->set_secure(secure);
  }

  void ExpectCrypto(cricket::SecurePolicy secure) {
    MakeSignalingSecure(secure);
    expect_incoming_crypto_ = true;
#ifdef HAVE_SRTP
    expect_outgoing_crypto_ = true;
#endif
  }

  void ExpectVideoBandwidth(int bandwidth) {
    expected_video_bandwidth_ = bandwidth;
  }

  void ExpectVideoRtcpMux(bool rtcp_mux) {
    expected_video_rtcp_mux_ = rtcp_mux;
  }

  template <class C>
  void SetCodecFeedbackParams(std::vector<C>* codecs,
                              const FeedbackParams& fb_params) {
    for (size_t i = 0; i < codecs->size(); ++i) {
      codecs->at(i).feedback_params = fb_params;
    }
  }

  void ExpectRtcpFb() {
    FeedbackParams params_nack_fir;
    params_nack_fir.Add(FeedbackParam(cricket::kRtcpFbParamCcm,
                                      cricket::kRtcpFbCcmParamFir));
    params_nack_fir.Add(FeedbackParam(cricket::kRtcpFbParamNack));

    FeedbackParams params_nack;
    params_nack.Add(FeedbackParam(cricket::kRtcpFbParamNack));

    expected_audio_fb_params_ = params_nack;
    expected_video_fb_params_ = params_nack_fir;
    expected_data_fb_params_ = params_nack;
  }

  cricket::FakeMediaEngine* fme() { return fme_; }

 private:
  void OnSendStanza(cricket::SessionManager* manager,
                    const buzz::XmlElement* stanza) {
    LOG(LS_INFO) << stanza->Str();
    stanzas_.push_back(new buzz::XmlElement(*stanza));
  }

  void OnSessionCreate(cricket::Session* session, bool initiate) {
    session->set_current_protocol(initial_protocol_);
  }

  void OnCallCreate(cricket::Call *call) {
    call_ = call;
    call->SignalMediaStreamsUpdate.connect(
        this, &MediaSessionClientTest::OnMediaStreamsUpdate);
  }

  void OnCallDestroy(cricket::Call *call) {
    call_ = NULL;
  }

  void OnMediaStreamsUpdate(cricket::Call *call,
                            cricket::Session *session,
                            const cricket::MediaStreams& added,
                            const cricket::MediaStreams& removed) {
    last_streams_added_.CopyFrom(added);
    last_streams_removed_.CopyFrom(removed);
  }

  rtc::NetworkManager* nm_;
  cricket::PortAllocator* pa_;
  cricket::SessionManager* sm_;
  cricket::FakeMediaEngine* fme_;
  cricket::FakeDataEngine* fdme_;
  cricket::MediaSessionClient* client_;

  cricket::Call* call_;
  std::vector<buzz::XmlElement* > stanzas_;
  MediaSessionTestParser* parser_;
  cricket::SignalingProtocol initial_protocol_;
  bool expect_incoming_crypto_;
  bool expect_outgoing_crypto_;
  int expected_video_bandwidth_;
  bool expected_video_rtcp_mux_;
  FeedbackParams expected_audio_fb_params_;
  FeedbackParams expected_video_fb_params_;
  FeedbackParams expected_data_fb_params_;
  cricket::MediaStreams last_streams_added_;
  cricket::MediaStreams last_streams_removed_;
};

MediaSessionClientTest* GingleTest() {
  return new MediaSessionClientTest(new GingleSessionTestParser(),
                                    cricket::PROTOCOL_GINGLE);
}

MediaSessionClientTest* JingleTest() {
  return new MediaSessionClientTest(new JingleSessionTestParser(),
                                    cricket::PROTOCOL_JINGLE);
}

TEST(MediaSessionTest, JingleGoodInitiateWithRtcpFb) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  rtc::scoped_ptr<buzz::XmlElement> elem;

  cricket::CallOptions options = VideoCallOptions();
  options.data_channel_type = cricket::DCT_SCTP;
  test->ExpectRtcpFb();
  test->TestGoodIncomingInitiate(
      kJingleInitiateWithRtcpFb, options, elem.use());
}

TEST(MediaSessionTest, JingleGoodVideoInitiate) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  rtc::scoped_ptr<buzz::XmlElement> elem;
  test->TestGoodIncomingInitiate(
      kJingleVideoInitiate, VideoCallOptions(), elem.use());
  test->TestCodecsOfVideoInitiate(elem.get());
}

TEST(MediaSessionTest, JingleGoodVideoInitiateWithBandwidth) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  rtc::scoped_ptr<buzz::XmlElement> elem;
  test->ExpectVideoBandwidth(42000);
  test->TestGoodIncomingInitiate(
      kJingleVideoInitiateWithBandwidth, VideoCallOptions(), elem.use());
}

TEST(MediaSessionTest, JingleGoodVideoInitiateWithRtcpMux) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  rtc::scoped_ptr<buzz::XmlElement> elem;
  test->ExpectVideoRtcpMux(true);
  test->TestGoodIncomingInitiate(
      kJingleVideoInitiateWithRtcpMux, VideoCallOptions(), elem.use());
}

TEST(MediaSessionTest, JingleGoodVideoInitiateWithRtpData) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  rtc::scoped_ptr<buzz::XmlElement> elem;
  cricket::CallOptions options = VideoCallOptions();
  options.data_channel_type = cricket::DCT_RTP;
  test->TestGoodIncomingInitiate(
      AddEncryption(kJingleVideoInitiateWithRtpData, kJingleCryptoOffer),
      options,
      elem.use());
}

TEST(MediaSessionTest, JingleGoodVideoInitiateWithSctpData) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  rtc::scoped_ptr<buzz::XmlElement> elem;
  cricket::CallOptions options = VideoCallOptions();
  options.data_channel_type = cricket::DCT_SCTP;
  test->TestGoodIncomingInitiate(kJingleVideoInitiateWithSctpData,
                                 options,
                                 elem.use());
}

TEST(MediaSessionTest, JingleRejectAudio) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  rtc::scoped_ptr<buzz::XmlElement> elem;
  cricket::CallOptions options = VideoCallOptions();
  options.has_audio = false;
  options.data_channel_type = cricket::DCT_RTP;
  test->TestRejectOffer(kJingleVideoInitiateWithRtpData, options, elem.use());
}

TEST(MediaSessionTest, JingleRejectVideo) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  rtc::scoped_ptr<buzz::XmlElement> elem;
  cricket::CallOptions options = AudioCallOptions();
  options.data_channel_type = cricket::DCT_RTP;
  test->TestRejectOffer(kJingleVideoInitiateWithRtpData, options, elem.use());
}

TEST(MediaSessionTest, JingleRejectData) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  rtc::scoped_ptr<buzz::XmlElement> elem;
  test->TestRejectOffer(
      kJingleVideoInitiateWithRtpData, VideoCallOptions(), elem.use());
}

TEST(MediaSessionTest, JingleRejectVideoAndData) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  rtc::scoped_ptr<buzz::XmlElement> elem;
  test->TestRejectOffer(
      kJingleVideoInitiateWithRtpData, AudioCallOptions(), elem.use());
}

TEST(MediaSessionTest, JingleGoodInitiateAllSupportedAudioCodecs) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  rtc::scoped_ptr<buzz::XmlElement> elem;
  test->TestGoodIncomingInitiate(
      kJingleInitiate, AudioCallOptions(), elem.use());
  test->TestHasAllSupportedAudioCodecs(elem.get());
}

// Changes the codecs that our FakeMediaEngine will support with a different
// preference order than the incoming offer.
// Verifies the answer accepts the preference order of the remote peer.
TEST(MediaSessionTest, JingleGoodInitiateDifferentPreferenceAudioCodecs) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  test->fme()->SetAudioCodecs(MAKE_VECTOR(kAudioCodecsDifferentPreference));
  rtc::scoped_ptr<buzz::XmlElement> elem;
  test->TestGoodIncomingInitiate(
      kJingleInitiate, AudioCallOptions(), elem.use());
  test->TestHasAllSupportedAudioCodecs(elem.get());
}

TEST(MediaSessionTest, JingleGoodInitiateSomeUnsupportedAudioCodecs) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  rtc::scoped_ptr<buzz::XmlElement> elem;
  test->TestGoodIncomingInitiate(
      kJingleInitiateSomeUnsupported, AudioCallOptions(), elem.use());
  test->TestHasAudioCodecsFromInitiateSomeUnsupported(elem.get());
}

TEST(MediaSessionTest, JingleGoodInitiateDynamicAudioCodecs) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  rtc::scoped_ptr<buzz::XmlElement> elem;
  test->TestGoodIncomingInitiate(
      kJingleInitiateDynamicAudioCodecs, AudioCallOptions(), elem.use());
  test->TestHasAudioCodecsFromInitiateDynamicAudioCodecs(elem.get());
}

TEST(MediaSessionTest, JingleGoodInitiateStaticAudioCodecs) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  rtc::scoped_ptr<buzz::XmlElement> elem;
  test->TestGoodIncomingInitiate(
      kJingleInitiateStaticAudioCodecs, AudioCallOptions(), elem.use());
  test->TestHasAudioCodecsFromInitiateStaticAudioCodecs(elem.get());
}

TEST(MediaSessionTest, JingleBadInitiateNoAudioCodecs) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  test->TestBadIncomingInitiate(kJingleInitiateNoAudioCodecs);
}

TEST(MediaSessionTest, JingleBadInitiateNoSupportedAudioCodecs) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  test->TestBadIncomingInitiate(kJingleInitiateNoSupportedAudioCodecs);
}

TEST(MediaSessionTest, JingleBadInitiateWrongClockrates) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  test->TestBadIncomingInitiate(kJingleInitiateWrongClockrates);
}

TEST(MediaSessionTest, JingleBadInitiateWrongChannels) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  test->TestBadIncomingInitiate(kJingleInitiateWrongChannels);
}

TEST(MediaSessionTest, JingleBadInitiateNoPayloadTypes) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  test->TestBadIncomingInitiate(kJingleInitiateNoPayloadTypes);
}

TEST(MediaSessionTest, JingleBadInitiateDynamicWithoutNames) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  test->TestBadIncomingInitiate(kJingleInitiateDynamicWithoutNames);
}

TEST(MediaSessionTest, JingleGoodOutgoingInitiate) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  test->TestGoodOutgoingInitiate(AudioCallOptions());
}

TEST(MediaSessionTest, JingleGoodOutgoingInitiateWithBandwidth) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  cricket::CallOptions options = VideoCallOptions();
  options.video_bandwidth = 42000;
  test->TestGoodOutgoingInitiate(options);
}

TEST(MediaSessionTest, JingleGoodOutgoingInitiateWithRtcpMux) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  cricket::CallOptions options = VideoCallOptions();
  options.rtcp_mux_enabled = true;
  test->TestGoodOutgoingInitiate(options);
}

TEST(MediaSessionTest, JingleGoodOutgoingInitiateWithRtpData) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  cricket::CallOptions options;
  options.data_channel_type = cricket::DCT_RTP;
  test->ExpectCrypto(cricket::SEC_ENABLED);
  test->TestGoodOutgoingInitiate(options);
}

TEST(MediaSessionTest, JingleGoodOutgoingInitiateWithSctpData) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  cricket::CallOptions options;
  options.data_channel_type = cricket::DCT_SCTP;
  test->TestGoodOutgoingInitiate(options);
}

// Crypto related tests.

// Offer has crypto but the session is not secured, just ignore it.
TEST(MediaSessionTest, JingleInitiateWithCryptoIsIgnoredWhenNotSecured) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  rtc::scoped_ptr<buzz::XmlElement> elem;
  test->TestGoodIncomingInitiate(
      AddEncryption(kJingleVideoInitiate, kJingleCryptoOffer),
      VideoCallOptions(),
      elem.use());
}

// Offer has crypto required but the session is not secure, fail.
TEST(MediaSessionTest, JingleInitiateWithCryptoRequiredWhenNotSecured) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  test->TestBadIncomingInitiate(AddEncryption(kJingleVideoInitiate,
                                             kJingleRequiredCryptoOffer));
}

// Offer has no crypto but the session is secure required, fail.
TEST(MediaSessionTest, JingleInitiateWithNoCryptoFailsWhenSecureRequired) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  test->ExpectCrypto(cricket::SEC_REQUIRED);
  test->TestBadIncomingInitiate(kJingleInitiate);
}

// Offer has crypto and session is secure, expect crypto in the answer.
TEST(MediaSessionTest, JingleInitiateWithCryptoWhenSecureEnabled) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  rtc::scoped_ptr<buzz::XmlElement> elem;
  test->ExpectCrypto(cricket::SEC_ENABLED);
  test->TestGoodIncomingInitiate(
      AddEncryption(kJingleVideoInitiate, kJingleCryptoOffer),
      VideoCallOptions(),
      elem.use());
}

// Offer has crypto and session is secure required, expect crypto in
// the answer.
TEST(MediaSessionTest, JingleInitiateWithCryptoWhenSecureRequired) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  rtc::scoped_ptr<buzz::XmlElement> elem;
  test->ExpectCrypto(cricket::SEC_REQUIRED);
  test->TestGoodIncomingInitiate(
      AddEncryption(kJingleVideoInitiate, kJingleCryptoOffer),
      VideoCallOptions(),
      elem.use());
}

// Offer has unsupported crypto and session is secure, no crypto in
// the answer.
TEST(MediaSessionTest, JingleInitiateWithUnsupportedCrypto) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  rtc::scoped_ptr<buzz::XmlElement> elem;
  test->MakeSignalingSecure(cricket::SEC_ENABLED);
  test->TestGoodIncomingInitiate(
      AddEncryption(kJingleInitiate, kJingleUnsupportedCryptoOffer),
      VideoCallOptions(),
      elem.use());
}

// Offer has unsupported REQUIRED crypto and session is not secure, fail.
TEST(MediaSessionTest, JingleInitiateWithRequiredUnsupportedCrypto) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  test->TestBadIncomingInitiate(
      AddEncryption(kJingleInitiate, kJingleRequiredUnsupportedCryptoOffer));
}

// Offer has unsupported REQUIRED crypto and session is secure, fail.
TEST(MediaSessionTest, JingleInitiateWithRequiredUnsupportedCryptoWhenSecure) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  test->MakeSignalingSecure(cricket::SEC_ENABLED);
  test->TestBadIncomingInitiate(
      AddEncryption(kJingleInitiate, kJingleRequiredUnsupportedCryptoOffer));
}

// Offer has unsupported REQUIRED crypto and session is required secure, fail.
TEST(MediaSessionTest,
     JingleInitiateWithRequiredUnsupportedCryptoWhenSecureRequired) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  test->MakeSignalingSecure(cricket::SEC_REQUIRED);
  test->TestBadIncomingInitiate(
      AddEncryption(kJingleInitiate, kJingleRequiredUnsupportedCryptoOffer));
}


TEST(MediaSessionTest, JingleGoodOutgoingInitiateWithCrypto) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  test->ExpectCrypto(cricket::SEC_ENABLED);
  test->TestGoodOutgoingInitiate(AudioCallOptions());
}

TEST(MediaSessionTest, JingleGoodOutgoingInitiateWithCryptoRequired) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  test->ExpectCrypto(cricket::SEC_REQUIRED);
  test->TestGoodOutgoingInitiate(AudioCallOptions());
}

TEST(MediaSessionTest, JingleIncomingAcceptWithSsrcs) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  cricket::CallOptions options = VideoCallOptions();
  options.is_muc = true;
  test->TestIncomingAcceptWithSsrcs(kJingleAcceptWithSsrcs, options);
}

TEST(MediaSessionTest, JingleIncomingAcceptWithRtpDataSsrcs) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  cricket::CallOptions options = VideoCallOptions();
  options.is_muc = true;
  options.data_channel_type = cricket::DCT_RTP;
  test->TestIncomingAcceptWithSsrcs(kJingleAcceptWithRtpDataSsrcs, options);
}

TEST(MediaSessionTest, JingleIncomingAcceptWithSctpData) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  cricket::CallOptions options = VideoCallOptions();
  options.is_muc = true;
  options.data_channel_type = cricket::DCT_SCTP;
  test->TestIncomingAcceptWithSsrcs(kJingleAcceptWithSctpData, options);
}

TEST(MediaSessionTest, JingleStreamsUpdateAndView) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  test->TestStreamsUpdateAndViewRequests();
}

TEST(MediaSessionTest, JingleSendVideoStreamUpdate) {
  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
  test->TestSendVideoStreamUpdate();
}

// Gingle tests

TEST(MediaSessionTest, GingleGoodVideoInitiate) {
  rtc::scoped_ptr<buzz::XmlElement> elem;
  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
  test->TestGoodIncomingInitiate(
      kGingleVideoInitiate, VideoCallOptions(), elem.use());
  test->TestCodecsOfVideoInitiate(elem.get());
}

TEST(MediaSessionTest, GingleGoodVideoInitiateWithBandwidth) {
  rtc::scoped_ptr<buzz::XmlElement> elem;
  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
  test->ExpectVideoBandwidth(42000);
  test->TestGoodIncomingInitiate(
      kGingleVideoInitiateWithBandwidth, VideoCallOptions(), elem.use());
}

TEST(MediaSessionTest, GingleGoodInitiateAllSupportedAudioCodecs) {
  rtc::scoped_ptr<buzz::XmlElement> elem;
  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
  test->TestGoodIncomingInitiate(
      kGingleInitiate, AudioCallOptions(), elem.use());
  test->TestHasAllSupportedAudioCodecs(elem.get());
}

TEST(MediaSessionTest, GingleGoodInitiateAllSupportedAudioCodecsWithCrypto) {
  rtc::scoped_ptr<buzz::XmlElement> elem;
  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
  test->ExpectCrypto(cricket::SEC_ENABLED);
  test->TestGoodIncomingInitiate(
      AddEncryption(kGingleInitiate, kGingleCryptoOffer),
      AudioCallOptions(),
      elem.use());
  test->TestHasAllSupportedAudioCodecs(elem.get());
}

// Changes the codecs that our FakeMediaEngine will support with a different
// preference order than the incoming offer.
// Verifies the answer accepts the preference order of the remote peer.
TEST(MediaSessionTest, GingleGoodInitiateDifferentPreferenceAudioCodecs) {
  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
  test->fme()->SetAudioCodecs(MAKE_VECTOR(kAudioCodecsDifferentPreference));
  rtc::scoped_ptr<buzz::XmlElement> elem;
  test->TestGoodIncomingInitiate(
      kGingleInitiate, AudioCallOptions(), elem.use());
  test->TestHasAllSupportedAudioCodecs(elem.get());
}

TEST(MediaSessionTest, GingleGoodInitiateSomeUnsupportedAudioCodecs) {
  rtc::scoped_ptr<buzz::XmlElement> elem;
  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
  test->TestGoodIncomingInitiate(
      kGingleInitiateSomeUnsupported, AudioCallOptions(), elem.use());
  test->TestHasAudioCodecsFromInitiateSomeUnsupported(elem.get());
}

TEST(MediaSessionTest, GingleGoodInitiateDynamicAudioCodecs) {
  rtc::scoped_ptr<buzz::XmlElement> elem;
  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
  test->TestGoodIncomingInitiate(
      kGingleInitiateDynamicAudioCodecs, AudioCallOptions(), elem.use());
  test->TestHasAudioCodecsFromInitiateDynamicAudioCodecs(elem.get());
}

TEST(MediaSessionTest, GingleGoodInitiateStaticAudioCodecs) {
  rtc::scoped_ptr<buzz::XmlElement> elem;
  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
  test->TestGoodIncomingInitiate(
      kGingleInitiateStaticAudioCodecs, AudioCallOptions(), elem.use());
  test->TestHasAudioCodecsFromInitiateStaticAudioCodecs(elem.get());
}

TEST(MediaSessionTest, GingleGoodInitiateNoAudioCodecs) {
  rtc::scoped_ptr<buzz::XmlElement> elem;
  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
  test->TestGoodIncomingInitiate(
      kGingleInitiateNoAudioCodecs, AudioCallOptions(), elem.use());
  test->TestHasDefaultAudioCodecs(elem.get());
}

TEST(MediaSessionTest, GingleBadInitiateNoSupportedAudioCodecs) {
  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
  test->TestBadIncomingInitiate(kGingleInitiateNoSupportedAudioCodecs);
}

TEST(MediaSessionTest, GingleBadInitiateWrongClockrates) {
  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
  test->TestBadIncomingInitiate(kGingleInitiateWrongClockrates);
}

TEST(MediaSessionTest, GingleBadInitiateWrongChannels) {
  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
  test->TestBadIncomingInitiate(kGingleInitiateWrongChannels);
}


TEST(MediaSessionTest, GingleBadInitiateNoPayloadTypes) {
  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
  test->TestBadIncomingInitiate(kGingleInitiateNoPayloadTypes);
}

TEST(MediaSessionTest, GingleBadInitiateDynamicWithoutNames) {
  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
  test->TestBadIncomingInitiate(kGingleInitiateDynamicWithoutNames);
}

TEST(MediaSessionTest, GingleGoodOutgoingInitiate) {
  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
  test->TestGoodOutgoingInitiate(AudioCallOptions());
}

TEST(MediaSessionTest, GingleGoodOutgoingInitiateWithBandwidth) {
  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
  cricket::CallOptions options = VideoCallOptions();
  options.video_bandwidth = 42000;
  test->TestGoodOutgoingInitiate(options);
}

// Crypto related tests.

// Offer has crypto but the session is not secured, just ignore it.
TEST(MediaSessionTest, GingleInitiateWithCryptoIsIgnoredWhenNotSecured) {
  rtc::scoped_ptr<buzz::XmlElement> elem;
  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
  test->TestGoodIncomingInitiate(
      AddEncryption(kGingleInitiate, kGingleCryptoOffer),
      VideoCallOptions(),
      elem.use());
}

// Offer has crypto required but the session is not secure, fail.
TEST(MediaSessionTest, GingleInitiateWithCryptoRequiredWhenNotSecured) {
  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
  test->TestBadIncomingInitiate(AddEncryption(kGingleInitiate,
                                             kGingleRequiredCryptoOffer));
}

// Offer has no crypto but the session is secure required, fail.
TEST(MediaSessionTest, GingleInitiateWithNoCryptoFailsWhenSecureRequired) {
  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
  test->ExpectCrypto(cricket::SEC_REQUIRED);
  test->TestBadIncomingInitiate(kGingleInitiate);
}

// Offer has crypto and session is secure, expect crypto in the answer.
TEST(MediaSessionTest, GingleInitiateWithCryptoWhenSecureEnabled) {
  rtc::scoped_ptr<buzz::XmlElement> elem;
  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
  test->ExpectCrypto(cricket::SEC_ENABLED);
  test->TestGoodIncomingInitiate(
      AddEncryption(kGingleInitiate, kGingleCryptoOffer),
      VideoCallOptions(),
      elem.use());
}

// Offer has crypto and session is secure required, expect crypto in
// the answer.
TEST(MediaSessionTest, GingleInitiateWithCryptoWhenSecureRequired) {
  rtc::scoped_ptr<buzz::XmlElement> elem;
  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
  test->ExpectCrypto(cricket::SEC_REQUIRED);
  test->TestGoodIncomingInitiate(
      AddEncryption(kGingleInitiate, kGingleCryptoOffer),
      VideoCallOptions(),
      elem.use());
}

// Offer has unsupported crypto and session is secure, no crypto in
// the answer.
TEST(MediaSessionTest, GingleInitiateWithUnsupportedCrypto) {
  rtc::scoped_ptr<buzz::XmlElement> elem;
  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
  test->MakeSignalingSecure(cricket::SEC_ENABLED);
  test->TestGoodIncomingInitiate(
      AddEncryption(kGingleInitiate, kGingleUnsupportedCryptoOffer),
      VideoCallOptions(),
      elem.use());
}

// Offer has unsupported REQUIRED crypto and session is not secure, fail.
TEST(MediaSessionTest, GingleInitiateWithRequiredUnsupportedCrypto) {
  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
  test->TestBadIncomingInitiate(
      AddEncryption(kGingleInitiate, kGingleRequiredUnsupportedCryptoOffer));
}

// Offer has unsupported REQUIRED crypto and session is secure, fail.
TEST(MediaSessionTest, GingleInitiateWithRequiredUnsupportedCryptoWhenSecure) {
  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
  test->MakeSignalingSecure(cricket::SEC_ENABLED);
  test->TestBadIncomingInitiate(
      AddEncryption(kGingleInitiate, kGingleRequiredUnsupportedCryptoOffer));
}

// Offer has unsupported REQUIRED crypto and session is required secure, fail.
TEST(MediaSessionTest,
     GingleInitiateWithRequiredUnsupportedCryptoWhenSecureRequired) {
  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
  test->MakeSignalingSecure(cricket::SEC_REQUIRED);
  test->TestBadIncomingInitiate(
      AddEncryption(kGingleInitiate, kGingleRequiredUnsupportedCryptoOffer));
}

TEST(MediaSessionTest, GingleGoodOutgoingInitiateWithCrypto) {
  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
  test->ExpectCrypto(cricket::SEC_ENABLED);
  test->TestGoodOutgoingInitiate(AudioCallOptions());
}

TEST(MediaSessionTest, GingleGoodOutgoingInitiateWithCryptoRequired) {
  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
  test->ExpectCrypto(cricket::SEC_REQUIRED);
  test->TestGoodOutgoingInitiate(AudioCallOptions());
}

TEST(MediaSessionTest, GingleIncomingAcceptWithSsrcs) {
  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
  cricket::CallOptions options = VideoCallOptions();
  options.is_muc = true;
  test->TestIncomingAcceptWithSsrcs(kGingleAcceptWithSsrcs, options);
}

TEST(MediaSessionTest, GingleGoodOutgoingInitiateWithRtpData) {
  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
  cricket::CallOptions options;
  options.data_channel_type = cricket::DCT_RTP;
  test->ExpectCrypto(cricket::SEC_ENABLED);
  test->TestGoodOutgoingInitiate(options);
}
