Move the obvious/easy Jingle-specific code into webrtc/libjingle.

R=tommi@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/32669004

git-svn-id: http://webrtc.googlecode.com/svn/trunk/webrtc@7886 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/libjingle/xmpp/OWNERS b/libjingle/OWNERS
similarity index 100%
rename from libjingle/xmpp/OWNERS
rename to libjingle/OWNERS
diff --git a/libjingle/examples/call/Info.plist b/libjingle/examples/call/Info.plist
new file mode 100644
index 0000000..a59cfa5
--- /dev/null
+++ b/libjingle/examples/call/Info.plist
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+  <key>CFBundleIdentifier</key>
+  <string>com.google.call</string>
+  <key>CFBundleName</key>
+  <string>call</string>
+</dict>
+</plist>
+
diff --git a/libjingle/examples/call/call_main.cc b/libjingle/examples/call/call_main.cc
new file mode 100644
index 0000000..7cdea85
--- /dev/null
+++ b/libjingle/examples/call/call_main.cc
@@ -0,0 +1,497 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, 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 <stdio.h>
+#include <string.h>
+#include <time.h>
+
+#include <iomanip>
+#include <iostream>
+#include <vector>
+
+#include "webrtc/base/flags.h"
+#include "webrtc/base/logging.h"
+#ifdef OSX
+#include "webrtc/base/maccocoasocketserver.h"
+#endif
+#include "talk/examples/call/callclient.h"
+#include "talk/examples/call/console.h"
+#include "talk/examples/call/mediaenginefactory.h"
+#include "webrtc/p2p/base/constants.h"
+#include "webrtc/libjingle/media/mediasessionclient.h"
+#include "talk/session/media/srtpfilter.h"
+#include "webrtc/libjingle/xmpp/xmppauth.h"
+#include "webrtc/libjingle/xmpp/xmppclientsettings.h"
+#include "webrtc/libjingle/xmpp/xmpppump.h"
+#include "webrtc/libjingle/xmpp/xmppsocket.h"
+#include "webrtc/base/pathutils.h"
+#include "webrtc/base/ssladapter.h"
+#include "webrtc/base/stream.h"
+#include "webrtc/base/win32socketserver.h"
+
+class DebugLog : public sigslot::has_slots<> {
+ public:
+  DebugLog() :
+    debug_input_buf_(NULL), debug_input_len_(0), debug_input_alloc_(0),
+    debug_output_buf_(NULL), debug_output_len_(0), debug_output_alloc_(0),
+    censor_password_(false)
+      {}
+  char * debug_input_buf_;
+  int debug_input_len_;
+  int debug_input_alloc_;
+  char * debug_output_buf_;
+  int debug_output_len_;
+  int debug_output_alloc_;
+  bool censor_password_;
+
+  void Input(const char * data, int len) {
+    if (debug_input_len_ + len > debug_input_alloc_) {
+      char * old_buf = debug_input_buf_;
+      debug_input_alloc_ = 4096;
+      while (debug_input_alloc_ < debug_input_len_ + len) {
+        debug_input_alloc_ *= 2;
+      }
+      debug_input_buf_ = new char[debug_input_alloc_];
+      memcpy(debug_input_buf_, old_buf, debug_input_len_);
+      delete[] old_buf;
+    }
+    memcpy(debug_input_buf_ + debug_input_len_, data, len);
+    debug_input_len_ += len;
+    DebugPrint(debug_input_buf_, &debug_input_len_, false);
+  }
+
+  void Output(const char * data, int len) {
+    if (debug_output_len_ + len > debug_output_alloc_) {
+      char * old_buf = debug_output_buf_;
+      debug_output_alloc_ = 4096;
+      while (debug_output_alloc_ < debug_output_len_ + len) {
+        debug_output_alloc_ *= 2;
+      }
+      debug_output_buf_ = new char[debug_output_alloc_];
+      memcpy(debug_output_buf_, old_buf, debug_output_len_);
+      delete[] old_buf;
+    }
+    memcpy(debug_output_buf_ + debug_output_len_, data, len);
+    debug_output_len_ += len;
+    DebugPrint(debug_output_buf_, &debug_output_len_, true);
+  }
+
+  static bool IsAuthTag(const char * str, size_t len) {
+    if (str[0] == '<' && str[1] == 'a' &&
+                         str[2] == 'u' &&
+                         str[3] == 't' &&
+                         str[4] == 'h' &&
+                         str[5] <= ' ') {
+      std::string tag(str, len);
+
+      if (tag.find("mechanism") != std::string::npos)
+        return true;
+    }
+    return false;
+  }
+
+  void DebugPrint(char * buf, int * plen, bool output) {
+    int len = *plen;
+    if (len > 0) {
+      time_t tim = time(NULL);
+      struct tm * now = localtime(&tim);
+      char *time_string = asctime(now);
+      if (time_string) {
+        size_t time_len = strlen(time_string);
+        if (time_len > 0) {
+          time_string[time_len-1] = 0;    // trim off terminating \n
+        }
+      }
+      LOG(INFO) << (output ? "SEND >>>>>>>>>>>>>>>>" : "RECV <<<<<<<<<<<<<<<<")
+                << " : " << time_string;
+
+      bool indent;
+      int start = 0, nest = 3;
+      for (int i = 0; i < len; i += 1) {
+        if (buf[i] == '>') {
+          if ((i > 0) && (buf[i-1] == '/')) {
+            indent = false;
+          } else if ((start + 1 < len) && (buf[start + 1] == '/')) {
+            indent = false;
+            nest -= 2;
+          } else {
+            indent = true;
+          }
+
+          // Output a tag
+          LOG(INFO) << std::setw(nest) << " "
+                    << std::string(buf + start, i + 1 - start);
+
+          if (indent)
+            nest += 2;
+
+          // Note if it's a PLAIN auth tag
+          if (IsAuthTag(buf + start, i + 1 - start)) {
+            censor_password_ = true;
+          }
+
+          // incr
+          start = i + 1;
+        }
+
+        if (buf[i] == '<' && start < i) {
+          if (censor_password_) {
+            LOG(INFO) << std::setw(nest) << " " << "## TEXT REMOVED ##";
+            censor_password_ = false;
+          } else {
+            LOG(INFO) << std::setw(nest) << " "
+                      << std::string(buf + start, i - start);
+          }
+          start = i;
+        }
+      }
+      len = len - start;
+      memcpy(buf, buf + start, len);
+      *plen = len;
+    }
+  }
+};
+
+static DebugLog debug_log_;
+static const int DEFAULT_PORT = 5222;
+
+#ifdef ANDROID
+static std::vector<cricket::AudioCodec> codecs;
+static const cricket::AudioCodec ISAC(103, "ISAC", 40000, 16000, 1, 0);
+
+cricket::MediaEngineInterface *CreateAndroidMediaEngine() {
+    cricket::FakeMediaEngine *engine = new cricket::FakeMediaEngine();
+
+    codecs.push_back(ISAC);
+    engine->SetAudioCodecs(codecs);
+    return engine;
+}
+#endif
+
+// TODO: Move this into Console.
+void Print(const char* chars) {
+  printf("%s", chars);
+  fflush(stdout);
+}
+
+bool GetSecurePolicy(const std::string& in, cricket::SecurePolicy* out) {
+  if (in == "disable") {
+    *out = cricket::SEC_DISABLED;
+  } else if (in == "enable") {
+    *out = cricket::SEC_ENABLED;
+  } else if (in == "require") {
+    *out = cricket::SEC_REQUIRED;
+  } else {
+    return false;
+  }
+  return true;
+}
+
+int main(int argc, char **argv) {
+  // This app has three threads. The main thread will run the XMPP client,
+  // which will print to the screen in its own thread. A second thread
+  // will get input from the console, parse it, and pass the appropriate
+  // message back to the XMPP client's thread. A third thread is used
+  // by MediaSessionClient as its worker thread.
+
+  // define options
+  DEFINE_string(s, "talk.google.com", "The connection server to use.");
+  DEFINE_string(tls, "require",
+      "Select connection encryption: disable, enable, require.");
+  DEFINE_bool(allowplain, false, "Allow plain authentication.");
+  DEFINE_bool(testserver, false, "Use test server.");
+  DEFINE_string(oauth, "", "OAuth2 access token.");
+  DEFINE_bool(a, false, "Turn on auto accept for incoming calls.");
+  DEFINE_string(signaling, "hybrid",
+      "Initial signaling protocol to use: jingle, gingle, or hybrid.");
+  DEFINE_string(transport, "hybrid",
+      "Initial transport protocol to use: ice, gice, or hybrid.");
+  DEFINE_string(sdes, "enable",
+      "Select SDES media encryption: disable, enable, require.");
+  DEFINE_string(dtls, "disable",
+      "Select DTLS transport encryption: disable, enable, require.");
+  DEFINE_int(portallocator, 0, "Filter out unwanted connection types.");
+  DEFINE_string(pmuc, "groupchat.google.com", "The persistant muc domain.");
+  DEFINE_string(capsnode, "http://code.google.com/p/libjingle/call",
+                "Caps node: A URI identifying the app.");
+  DEFINE_string(capsver, "0.6",
+                "Caps ver: A string identifying the version of the app.");
+  DEFINE_string(voiceinput, NULL, "RTP dump file for voice input.");
+  DEFINE_string(voiceoutput, NULL, "RTP dump file for voice output.");
+  DEFINE_string(videoinput, NULL, "RTP dump file for video input.");
+  DEFINE_string(videooutput, NULL, "RTP dump file for video output.");
+  DEFINE_bool(render, true, "Renders the video.");
+  DEFINE_string(datachannel, "",
+                "Enable a data channel, and choose the type: rtp or sctp.");
+  DEFINE_bool(d, false, "Turn on debugging.");
+  DEFINE_string(log, "", "Turn on debugging to a file.");
+  DEFINE_bool(debugsrtp, false, "Enable debugging for srtp.");
+  DEFINE_bool(help, false, "Prints this message");
+  DEFINE_bool(multisession, false,
+              "Enable support for multiple sessions in calls.");
+  DEFINE_bool(roster, false,
+      "Enable roster messages printed in console.");
+
+  // parse options
+  rtc::FlagList::SetFlagsFromCommandLine(&argc, argv, true);
+  if (FLAG_help) {
+    rtc::FlagList::Print(NULL, false);
+    return 0;
+  }
+
+  bool auto_accept = FLAG_a;
+  bool debug = FLAG_d;
+  std::string log = FLAG_log;
+  std::string signaling = FLAG_signaling;
+  std::string transport = FLAG_transport;
+  bool test_server = FLAG_testserver;
+  bool allow_plain = FLAG_allowplain;
+  std::string tls = FLAG_tls;
+  std::string oauth_token = FLAG_oauth;
+  int32 portallocator_flags = FLAG_portallocator;
+  std::string pmuc_domain = FLAG_pmuc;
+  std::string server = FLAG_s;
+  std::string sdes = FLAG_sdes;
+  std::string dtls = FLAG_dtls;
+  std::string caps_node = FLAG_capsnode;
+  std::string caps_ver = FLAG_capsver;
+  bool debugsrtp = FLAG_debugsrtp;
+  bool render = FLAG_render;
+  std::string data_channel = FLAG_datachannel;
+  bool multisession_enabled = FLAG_multisession;
+  rtc::SSLIdentity* ssl_identity = NULL;
+  bool show_roster_messages = FLAG_roster;
+
+  // Set up debugging.
+  if (debug) {
+    rtc::LogMessage::LogToDebug(rtc::LS_VERBOSE);
+  }
+
+  if (!log.empty()) {
+    rtc::StreamInterface* stream =
+        rtc::Filesystem::OpenFile(log, "a");
+    if (stream) {
+      rtc::LogMessage::LogToStream(stream, rtc::LS_VERBOSE);
+    } else {
+      Print(("Cannot open debug log " + log + "\n").c_str());
+      return 1;
+    }
+  }
+
+  if (debugsrtp) {
+    cricket::EnableSrtpDebugging();
+  }
+
+  // Set up the crypto subsystem.
+  rtc::InitializeSSL();
+
+  // Parse username and password, if present.
+  buzz::Jid jid;
+  std::string username;
+  rtc::InsecureCryptStringImpl pass;
+  if (argc > 1) {
+    username = argv[1];
+    if (argc > 2) {
+      pass.password() = argv[2];
+    }
+  }
+
+  if (username.empty()) {
+    Print("JID: ");
+    std::cin >> username;
+  }
+  if (username.find('@') == std::string::npos) {
+    username.append("@localhost");
+  }
+  jid = buzz::Jid(username);
+  if (!jid.IsValid() || jid.node() == "") {
+    Print("Invalid JID. JIDs should be in the form user@domain\n");
+    return 1;
+  }
+  if (pass.password().empty() && !test_server && oauth_token.empty()) {
+    Console::SetEcho(false);
+    Print("Password: ");
+    std::cin >> pass.password();
+    Console::SetEcho(true);
+    Print("\n");
+  }
+
+  // Decide on the connection settings.
+  buzz::XmppClientSettings xcs;
+  xcs.set_user(jid.node());
+  xcs.set_resource("call");
+  xcs.set_host(jid.domain());
+  xcs.set_allow_plain(allow_plain);
+
+  if (tls == "disable") {
+    xcs.set_use_tls(buzz::TLS_DISABLED);
+  } else if (tls == "enable") {
+    xcs.set_use_tls(buzz::TLS_ENABLED);
+  } else if (tls == "require") {
+    xcs.set_use_tls(buzz::TLS_REQUIRED);
+  } else {
+    Print("Invalid TLS option, must be enable, disable, or require.\n");
+    return 1;
+  }
+
+  if (test_server) {
+    pass.password() = jid.node();
+    xcs.set_allow_plain(true);
+    xcs.set_use_tls(buzz::TLS_DISABLED);
+    xcs.set_test_server_domain("google.com");
+  }
+  xcs.set_pass(rtc::CryptString(pass));
+  if (!oauth_token.empty()) {
+    xcs.set_auth_token(buzz::AUTH_MECHANISM_OAUTH2, oauth_token);
+  }
+
+  std::string host;
+  int port;
+
+  int colon = server.find(':');
+  if (colon == -1) {
+    host = server;
+    port = DEFAULT_PORT;
+  } else {
+    host = server.substr(0, colon);
+    port = atoi(server.substr(colon + 1).c_str());
+  }
+
+  xcs.set_server(rtc::SocketAddress(host, port));
+
+  // Decide on the signaling and crypto settings.
+  cricket::SignalingProtocol signaling_protocol = cricket::PROTOCOL_HYBRID;
+  if (signaling == "jingle") {
+    signaling_protocol = cricket::PROTOCOL_JINGLE;
+  } else if (signaling == "gingle") {
+    signaling_protocol = cricket::PROTOCOL_GINGLE;
+  } else if (signaling == "hybrid") {
+    signaling_protocol = cricket::PROTOCOL_HYBRID;
+  } else {
+    Print("Invalid signaling protocol.  Must be jingle, gingle, or hybrid.\n");
+    return 1;
+  }
+
+  cricket::TransportProtocol transport_protocol = cricket::ICEPROTO_HYBRID;
+  if (transport == "ice") {
+    transport_protocol = cricket::ICEPROTO_RFC5245;
+  } else if (transport == "gice") {
+    transport_protocol = cricket::ICEPROTO_GOOGLE;
+  } else if (transport == "hybrid") {
+    transport_protocol = cricket::ICEPROTO_HYBRID;
+  } else {
+    Print("Invalid transport protocol.  Must be ice, gice, or hybrid.\n");
+    return 1;
+  }
+
+  cricket::DataChannelType data_channel_type = cricket::DCT_NONE;
+  if (data_channel == "rtp") {
+    data_channel_type = cricket::DCT_RTP;
+  } else if (data_channel == "sctp") {
+    data_channel_type = cricket::DCT_SCTP;
+  } else if (!data_channel.empty()) {
+    Print("Invalid data channel type.  Must be rtp or sctp.\n");
+    return 1;
+  }
+
+  cricket::SecurePolicy sdes_policy, dtls_policy;
+  if (!GetSecurePolicy(sdes, &sdes_policy)) {
+    Print("Invalid SDES policy. Must be enable, disable, or require.\n");
+    return 1;
+  }
+  if (!GetSecurePolicy(dtls, &dtls_policy)) {
+    Print("Invalid DTLS policy. Must be enable, disable, or require.\n");
+    return 1;
+  }
+  if (dtls_policy != cricket::SEC_DISABLED) {
+    ssl_identity = rtc::SSLIdentity::Generate(jid.Str());
+    if (!ssl_identity) {
+      Print("Failed to generate identity for DTLS.\n");
+      return 1;
+    }
+  }
+
+#ifdef ANDROID
+  MediaEngineFactory::SetCreateFunction(&CreateAndroidMediaEngine);
+#endif
+
+#if WIN32
+  // Need to pump messages on our main thread on Windows.
+  rtc::Win32Thread w32_thread;
+  rtc::ThreadManager::Instance()->SetCurrentThread(&w32_thread);
+#endif
+  rtc::Thread* main_thread = rtc::Thread::Current();
+#ifdef OSX
+  rtc::MacCocoaSocketServer ss;
+  rtc::SocketServerScope ss_scope(&ss);
+#endif
+
+  buzz::XmppPump pump;
+  CallClient *client = new CallClient(pump.client(), caps_node, caps_ver);
+
+  if (FLAG_voiceinput || FLAG_voiceoutput ||
+      FLAG_videoinput || FLAG_videooutput) {
+    // If any dump file is specified, we use a FileMediaEngine.
+    cricket::MediaEngineInterface* engine =
+        MediaEngineFactory::CreateFileMediaEngine(
+            FLAG_voiceinput, FLAG_voiceoutput,
+            FLAG_videoinput, FLAG_videooutput);
+    client->SetMediaEngine(engine);
+  }
+
+  Console *console = new Console(main_thread, client);
+  client->SetConsole(console);
+  client->SetAutoAccept(auto_accept);
+  client->SetPmucDomain(pmuc_domain);
+  client->SetPortAllocatorFlags(portallocator_flags);
+  client->SetAllowLocalIps(true);
+  client->SetSignalingProtocol(signaling_protocol);
+  client->SetTransportProtocol(transport_protocol);
+  client->SetSecurePolicy(sdes_policy, dtls_policy);
+  client->SetSslIdentity(ssl_identity);
+  client->SetRender(render);
+  client->SetDataChannelType(data_channel_type);
+  client->SetMultiSessionEnabled(multisession_enabled);
+  client->SetShowRosterMessages(show_roster_messages);
+  console->Start();
+
+  if (debug) {
+    pump.client()->SignalLogInput.connect(&debug_log_, &DebugLog::Input);
+    pump.client()->SignalLogOutput.connect(&debug_log_, &DebugLog::Output);
+  }
+
+  Print(("Logging in to " + server + " as " + jid.Str() + "\n").c_str());
+  pump.DoLogin(xcs, new buzz::XmppSocket(buzz::TLS_REQUIRED), new XmppAuth());
+  main_thread->Run();
+  pump.DoDisconnect();
+
+  console->Stop();
+  delete console;
+  delete client;
+
+  return 0;
+}
diff --git a/libjingle/examples/call/call_unittest.cc b/libjingle/examples/call/call_unittest.cc
new file mode 100644
index 0000000..cc2878f
--- /dev/null
+++ b/libjingle/examples/call/call_unittest.cc
@@ -0,0 +1,37 @@
+/*
+ * libjingle
+ * Copyright 2008, 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.
+ */
+
+// Main function for all unit tests in talk/examples/call
+
+#include "testing/base/public/gunit.h"
+#include "webrtc/base/logging.h"
+
+int main(int argc, char **argv) {
+  rtc::LogMessage::LogToDebug(rtc::LogMessage::NO_LOGGING);
+  testing::ParseGUnitFlags(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/libjingle/examples/call/callclient.cc b/libjingle/examples/call/callclient.cc
new file mode 100644
index 0000000..6eae983
--- /dev/null
+++ b/libjingle/examples/call/callclient.cc
@@ -0,0 +1,1616 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, 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 "talk/examples/call/callclient.h"
+
+#include <string>
+
+#include "talk/examples/call/console.h"
+#include "talk/examples/call/friendinvitesendtask.h"
+#include "talk/examples/call/muc.h"
+#include "talk/examples/call/mucinviterecvtask.h"
+#include "talk/examples/call/mucinvitesendtask.h"
+#include "talk/examples/call/presencepushtask.h"
+#include "talk/media/base/mediacommon.h"
+#include "talk/media/base/mediaengine.h"
+#include "talk/media/base/rtpdataengine.h"
+#include "talk/media/base/screencastid.h"
+#include "webrtc/base/helpers.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/network.h"
+#include "webrtc/base/socketaddress.h"
+#include "webrtc/base/stringencode.h"
+#include "webrtc/base/stringutils.h"
+#include "webrtc/base/thread.h"
+#include "webrtc/base/windowpickerfactory.h"
+#ifdef HAVE_SCTP
+#include "talk/media/sctp/sctpdataengine.h"
+#endif
+#include "talk/media/base/videorenderer.h"
+#include "talk/media/devices/devicemanager.h"
+#include "talk/media/devices/videorendererfactory.h"
+#include "webrtc/libjingle/session/sessionmanager.h"
+#include "webrtc/p2p/client/basicportallocator.h"
+#include "webrtc/p2p/client/sessionmanagertask.h"
+#include "webrtc/libjingle/media/mediamessages.h"
+#include "webrtc/libjingle/media/mediasessionclient.h"
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/libjingle/xmpp/hangoutpubsubclient.h"
+#include "webrtc/libjingle/xmpp/mucroomconfigtask.h"
+#include "webrtc/libjingle/xmpp/mucroomlookuptask.h"
+#include "webrtc/libjingle/xmpp/pingtask.h"
+#include "webrtc/libjingle/xmpp/presenceouttask.h"
+
+namespace {
+
+// Must be period >= timeout.
+const uint32 kPingPeriodMillis = 10000;
+const uint32 kPingTimeoutMillis = 10000;
+
+const char* DescribeStatus(buzz::PresenceStatus::Show show,
+                           const std::string& desc) {
+  switch (show) {
+  case buzz::PresenceStatus::SHOW_XA:      return desc.c_str();
+  case buzz::PresenceStatus::SHOW_ONLINE:  return "online";
+  case buzz::PresenceStatus::SHOW_AWAY:    return "away";
+  case buzz::PresenceStatus::SHOW_DND:     return "do not disturb";
+  case buzz::PresenceStatus::SHOW_CHAT:    return "ready to chat";
+  default:                                 return "offline";
+  }
+}
+
+std::string GetWord(const std::vector<std::string>& words,
+                    size_t index, const std::string& def) {
+  if (words.size() > index) {
+    return words[index];
+  } else {
+    return def;
+  }
+}
+
+int GetInt(const std::vector<std::string>& words, size_t index, int def) {
+  int val;
+  if (words.size() > index && rtc::FromString(words[index], &val)) {
+    return val;
+  } else {
+    return def;
+  }
+}
+
+}  // namespace
+
+const char* CALL_COMMANDS =
+"Available commands:\n"
+"\n"
+"  hangup            Ends the call.\n"
+"  hold              Puts the current call on hold\n"
+"  calls             Lists the current calls and their sessions\n"
+"  switch [call_id]  Switch to the specified call\n"
+"  addsession [jid]  Add a new session to the current call.\n"
+"  rmsession [sid]   Remove specified session.\n"
+"  mute              Stops sending voice.\n"
+"  unmute            Re-starts sending voice.\n"
+"  vmute             Stops sending video.\n"
+"  vunmute           Re-starts sending video.\n"
+"  dtmf              Sends a DTMF tone.\n"
+"  stats             Print voice stats for the current call.\n"
+"  quit              Quits the application.\n"
+"";
+
+// TODO: Make present and record really work.
+const char* HANGOUT_COMMANDS =
+"Available MUC commands:\n"
+"\n"
+"  present    Starts presenting (just signalling; not actually presenting.)\n"
+"  unpresent  Stops presenting (just signalling; not actually presenting.)\n"
+"  record     Starts recording (just signalling; not actually recording.)\n"
+"  unrecord   Stops recording (just signalling; not actually recording.)\n"
+"  rmute [nick] Remote mute another participant.\n"
+"  block [nick] Block another participant.\n"
+"  screencast [fps] Starts screencast. \n"
+"  unscreencast Stops screencast. \n"
+"  quit       Quits the application.\n"
+"";
+
+const char* RECEIVE_COMMANDS =
+"Available commands:\n"
+"\n"
+"  accept [bw] Accepts the incoming call and switches to it.\n"
+"  reject  Rejects the incoming call and stays with the current call.\n"
+"  quit    Quits the application.\n"
+"";
+
+const char* CONSOLE_COMMANDS =
+"Available commands:\n"
+"\n"
+"  roster              Prints the online friends from your roster.\n"
+"  friend user         Request to add a user to your roster.\n"
+"  call [jid] [bw]     Initiates a call to the user[/room] with the\n"
+"                      given JID and with optional bandwidth.\n"
+"  vcall [jid] [bw]    Initiates a video call to the user[/room] with\n"
+"                      the given JID and with optional bandwidth.\n"
+"  calls               Lists the current calls\n"
+"  switch [call_id]    Switch to the specified call\n"
+"  join [room_jid]     Joins a multi-user-chat with room JID.\n"
+"  ljoin [room_name]   Joins a MUC by looking up JID from room name.\n"
+"  invite user [room]  Invites a friend to a multi-user-chat.\n"
+"  leave [room]        Leaves a multi-user-chat.\n"
+"  nick [nick]         Sets the nick.\n"
+"  priority [int]      Sets the priority.\n"
+"  getdevs             Prints the available media devices.\n"
+"  quit                Quits the application.\n"
+"";
+
+void CallClient::ParseLine(const std::string& line) {
+  std::vector<std::string> words;
+  int start = -1;
+  int state = 0;
+  for (int index = 0; index <= static_cast<int>(line.size()); ++index) {
+    if (state == 0) {
+      if (!isspace(line[index])) {
+        start = index;
+        state = 1;
+      }
+    } else {
+      ASSERT(state == 1);
+      ASSERT(start >= 0);
+      if (isspace(line[index])) {
+        std::string word(line, start, index - start);
+        words.push_back(word);
+        start = -1;
+        state = 0;
+      }
+    }
+  }
+
+  // Global commands
+  const std::string& command = GetWord(words, 0, "");
+  if (command == "quit") {
+    Quit();
+  } else if (call_ && incoming_call_) {
+    if (command == "accept") {
+      cricket::CallOptions options;
+      options.video_bandwidth = GetInt(words, 1, cricket::kAutoBandwidth);
+      options.has_video = true;
+      options.data_channel_type = data_channel_type_;
+      Accept(options);
+    } else if (command == "reject") {
+      Reject();
+    } else {
+      console_->PrintLine(RECEIVE_COMMANDS);
+    }
+  } else if (call_) {
+    if (command == "hangup") {
+      call_->Terminate();
+    } else if (command == "hold") {
+      media_client_->SetFocus(NULL);
+      call_ = NULL;
+    } else if (command == "addsession") {
+      std::string to = GetWord(words, 1, "");
+      cricket::CallOptions options;
+      options.has_video = call_->has_video();
+      options.video_bandwidth = cricket::kAutoBandwidth;
+      options.data_channel_type = data_channel_type_;
+      options.AddStream(cricket::MEDIA_TYPE_VIDEO, "", "");
+      if (!InitiateAdditionalSession(to, options)) {
+        console_->PrintLine("Failed to initiate additional session.");
+      }
+    } else if (command == "rmsession") {
+      std::string id = GetWord(words, 1, "");
+      TerminateAndRemoveSession(call_, id);
+    } else if (command == "calls") {
+      PrintCalls();
+    } else if ((words.size() == 2) && (command == "switch")) {
+      SwitchToCall(GetInt(words, 1, -1));
+    } else if (command == "mute") {
+      call_->Mute(true);
+      if (InMuc()) {
+        hangout_pubsub_client_->PublishAudioMuteState(true);
+      }
+    } else if (command == "unmute") {
+      call_->Mute(false);
+      if (InMuc()) {
+        hangout_pubsub_client_->PublishAudioMuteState(false);
+      }
+    } else if (command == "vmute") {
+      call_->MuteVideo(true);
+      if (InMuc()) {
+        hangout_pubsub_client_->PublishVideoMuteState(true);
+      }
+    } else if (command == "vunmute") {
+      call_->MuteVideo(false);
+      if (InMuc()) {
+        hangout_pubsub_client_->PublishVideoMuteState(false);
+      }
+    } else if (command == "screencast") {
+      if (screencast_ssrc_ != 0) {
+        console_->PrintLine("Can't screencast twice.  Unscreencast first.");
+      } else {
+        std::string streamid = "screencast";
+        screencast_ssrc_ = rtc::CreateRandomId();
+        int fps = GetInt(words, 1, 5);  // Default to 5 fps.
+
+        cricket::ScreencastId screencastid;
+        cricket::Session* session = GetFirstSession();
+        if (session && SelectFirstDesktopScreencastId(&screencastid)) {
+          call_->StartScreencast(
+              session, streamid, screencast_ssrc_, screencastid, fps);
+        }
+      }
+    } else if (command == "unscreencast") {
+      // TODO: Use a random ssrc
+      std::string streamid = "screencast";
+
+      cricket::Session* session = GetFirstSession();
+      if (session) {
+        call_->StopScreencast(session, streamid, screencast_ssrc_);
+        screencast_ssrc_ = 0;
+      }
+    } else if (command == "present") {
+      if (InMuc()) {
+        hangout_pubsub_client_->PublishPresenterState(true);
+      }
+    } else if (command == "unpresent") {
+      if (InMuc()) {
+        hangout_pubsub_client_->PublishPresenterState(false);
+      }
+    } else if (command == "record") {
+      if (InMuc()) {
+        hangout_pubsub_client_->PublishRecordingState(true);
+      }
+    } else if (command == "unrecord") {
+      if (InMuc()) {
+        hangout_pubsub_client_->PublishRecordingState(false);
+      }
+    } else if ((command == "rmute") && (words.size() == 2)) {
+      if (InMuc()) {
+        const std::string& nick = words[1];
+        hangout_pubsub_client_->RemoteMute(nick);
+      }
+    } else if ((command == "block") && (words.size() == 2)) {
+      if (InMuc()) {
+        const std::string& nick = words[1];
+        hangout_pubsub_client_->BlockMedia(nick);
+      }
+    } else if (command == "senddata") {
+      // "" is the default streamid.
+      SendData("", words[1]);
+    } else if ((command == "dtmf") && (words.size() == 2)) {
+      int ev = std::string("0123456789*#").find(words[1][0]);
+      call_->PressDTMF(ev);
+    } else if (command == "stats") {
+      PrintStats();
+    } else {
+      console_->PrintLine(CALL_COMMANDS);
+      if (InMuc()) {
+        console_->PrintLine(HANGOUT_COMMANDS);
+      }
+    }
+  } else {
+    if (command == "roster") {
+      PrintRoster();
+    } else if (command == "send") {
+      buzz::Jid jid(words[1]);
+      if (jid.IsValid()) {
+        last_sent_to_ = words[1];
+        SendChat(words[1], words[2]);
+      } else if (!last_sent_to_.empty()) {
+        SendChat(last_sent_to_, words[1]);
+      } else {
+        console_->PrintLine(
+            "Invalid JID. JIDs should be in the form user@domain");
+      }
+    } else if ((words.size() == 2) && (command == "friend")) {
+      InviteFriend(words[1]);
+    } else if (command == "call") {
+      std::string to = GetWord(words, 1, "");
+      cricket::CallOptions options;
+      options.data_channel_type = data_channel_type_;
+      if (!PlaceCall(to, options)) {
+        console_->PrintLine("Failed to initiate call.");
+      }
+    } else if (command == "vcall") {
+      std::string to = GetWord(words, 1, "");
+      int bandwidth = GetInt(words, 2, cricket::kAutoBandwidth);
+      cricket::CallOptions options;
+      options.has_video = true;
+      options.video_bandwidth = bandwidth;
+      options.data_channel_type = data_channel_type_;
+      if (!PlaceCall(to, options)) {
+        console_->PrintLine("Failed to initiate call.");
+      }
+    } else if (command == "calls") {
+      PrintCalls();
+    } else if ((words.size() == 2) && (command == "switch")) {
+      SwitchToCall(GetInt(words, 1, -1));
+    } else if (command == "join") {
+      JoinMuc(GetWord(words, 1, ""));
+    } else if (command == "ljoin") {
+      LookupAndJoinMuc(GetWord(words, 1, ""));
+    } else if ((words.size() >= 2) && (command == "invite")) {
+      InviteToMuc(words[1], GetWord(words, 2, ""));
+    } else if (command == "leave") {
+      LeaveMuc(GetWord(words, 1, ""));
+    } else if (command == "nick") {
+      SetNick(GetWord(words, 1, ""));
+    } else if (command == "priority") {
+      int priority = GetInt(words, 1, 0);
+      SetPriority(priority);
+      SendStatus();
+    } else if (command == "getdevs") {
+      GetDevices();
+    } else if ((words.size() == 2) && (command == "setvol")) {
+      SetVolume(words[1]);
+    } else {
+      console_->PrintLine(CONSOLE_COMMANDS);
+    }
+  }
+}
+
+CallClient::CallClient(buzz::XmppClient* xmpp_client,
+                       const std::string& caps_node, const std::string& version)
+    : xmpp_client_(xmpp_client),
+      worker_thread_(NULL),
+      media_engine_(NULL),
+      data_engine_(NULL),
+      media_client_(NULL),
+      call_(NULL),
+      hangout_pubsub_client_(NULL),
+      incoming_call_(false),
+      auto_accept_(false),
+      pmuc_domain_("groupchat.google.com"),
+      render_(true),
+      data_channel_type_(cricket::DCT_NONE),
+      multisession_enabled_(false),
+      local_renderer_(NULL),
+      static_views_accumulated_count_(0),
+      screencast_ssrc_(0),
+      roster_(new RosterMap),
+      portallocator_flags_(0),
+      allow_local_ips_(false),
+      signaling_protocol_(cricket::PROTOCOL_HYBRID),
+      transport_protocol_(cricket::ICEPROTO_HYBRID),
+      sdes_policy_(cricket::SEC_DISABLED),
+      dtls_policy_(cricket::SEC_DISABLED),
+      ssl_identity_(),
+      show_roster_messages_(false) {
+  xmpp_client_->SignalStateChange.connect(this, &CallClient::OnStateChange);
+  my_status_.set_caps_node(caps_node);
+  my_status_.set_version(version);
+}
+
+CallClient::~CallClient() {
+  delete media_client_;
+  delete roster_;
+  delete worker_thread_;
+}
+
+const std::string CallClient::strerror(buzz::XmppEngine::Error err) {
+  switch (err) {
+    case buzz::XmppEngine::ERROR_NONE:
+      return "";
+    case buzz::XmppEngine::ERROR_XML:
+      return "Malformed XML or encoding error";
+    case buzz::XmppEngine::ERROR_STREAM:
+      return "XMPP stream error";
+    case buzz::XmppEngine::ERROR_VERSION:
+      return "XMPP version error";
+    case buzz::XmppEngine::ERROR_UNAUTHORIZED:
+      return "User is not authorized (Check your username and password)";
+    case buzz::XmppEngine::ERROR_TLS:
+      return "TLS could not be negotiated";
+    case buzz::XmppEngine::ERROR_AUTH:
+      return "Authentication could not be negotiated";
+    case buzz::XmppEngine::ERROR_BIND:
+      return "Resource or session binding could not be negotiated";
+    case buzz::XmppEngine::ERROR_CONNECTION_CLOSED:
+      return "Connection closed by output handler.";
+    case buzz::XmppEngine::ERROR_DOCUMENT_CLOSED:
+      return "Closed by </stream:stream>";
+    case buzz::XmppEngine::ERROR_SOCKET:
+      return "Socket error";
+    default:
+      return "Unknown error";
+  }
+}
+
+void CallClient::OnCallDestroy(cricket::Call* call) {
+  RemoveCallsStaticRenderedViews(call);
+  if (call == call_) {
+    if (local_renderer_) {
+      delete local_renderer_;
+      local_renderer_ = NULL;
+    }
+    console_->PrintLine("call destroyed");
+    call_ = NULL;
+    delete hangout_pubsub_client_;
+    hangout_pubsub_client_ = NULL;
+  }
+}
+
+void CallClient::OnStateChange(buzz::XmppEngine::State state) {
+  switch (state) {
+    case buzz::XmppEngine::STATE_START:
+      console_->PrintLine("connecting...");
+      break;
+    case buzz::XmppEngine::STATE_OPENING:
+      console_->PrintLine("logging in...");
+      break;
+    case buzz::XmppEngine::STATE_OPEN:
+      console_->PrintLine("logged in...");
+      InitMedia();
+      InitPresence();
+      break;
+    case buzz::XmppEngine::STATE_CLOSED:
+      {
+        buzz::XmppEngine::Error error = xmpp_client_->GetError(NULL);
+        console_->PrintLine("logged out... %s", strerror(error).c_str());
+        Quit();
+      }
+      break;
+    default:
+      break;
+  }
+}
+
+void CallClient::InitMedia() {
+  worker_thread_ = new rtc::Thread();
+  // The worker thread must be started here since initialization of
+  // the ChannelManager will generate messages that need to be
+  // dispatched by it.
+  worker_thread_->Start();
+
+  // TODO: It looks like we are leaking many objects. E.g.
+  // |network_manager_| is never deleted.
+  network_manager_ = new rtc::BasicNetworkManager();
+
+  // TODO: Decide if the relay address should be specified here.
+  rtc::SocketAddress stun_addr("stun.l.google.com", 19302);
+  cricket::ServerAddresses stun_servers;
+  stun_servers.insert(stun_addr);
+  port_allocator_ =  new cricket::BasicPortAllocator(
+      network_manager_, stun_servers, rtc::SocketAddress(),
+      rtc::SocketAddress(), rtc::SocketAddress());
+
+  if (portallocator_flags_ != 0) {
+    port_allocator_->set_flags(portallocator_flags_);
+  }
+  session_manager_ = new cricket::SessionManager(
+      port_allocator_, worker_thread_);
+  session_manager_->set_secure(dtls_policy_);
+  session_manager_->set_identity(ssl_identity_.get());
+  session_manager_->set_transport_protocol(transport_protocol_);
+  session_manager_->SignalRequestSignaling.connect(
+      this, &CallClient::OnRequestSignaling);
+  session_manager_->SignalSessionCreate.connect(
+      this, &CallClient::OnSessionCreate);
+  session_manager_->OnSignalingReady();
+
+  session_manager_task_ =
+      new cricket::SessionManagerTask(xmpp_client_, session_manager_);
+  session_manager_task_->EnableOutgoingMessages();
+  session_manager_task_->Start();
+
+  if (!media_engine_) {
+    media_engine_ = cricket::MediaEngineFactory::Create();
+  }
+
+  if (!data_engine_) {
+    if (data_channel_type_ == cricket::DCT_SCTP) {
+#ifdef HAVE_SCTP
+      data_engine_ = new cricket::SctpDataEngine();
+#else
+      LOG(LS_WARNING) << "SCTP Data Engine not supported.";
+      data_channel_type_ = cricket::DCT_NONE;
+      data_engine_ = new cricket::RtpDataEngine();
+#endif
+    } else {
+      // Even if we have DCT_NONE, we still have a data engine, just
+      // to make sure it isn't NULL.
+      data_engine_ = new cricket::RtpDataEngine();
+    }
+  }
+
+  media_client_ = new cricket::MediaSessionClient(
+      xmpp_client_->jid(),
+      session_manager_,
+      media_engine_,
+      data_engine_,
+      cricket::DeviceManagerFactory::Create());
+  media_client_->SignalCallCreate.connect(this, &CallClient::OnCallCreate);
+  media_client_->SignalCallDestroy.connect(this, &CallClient::OnCallDestroy);
+  media_client_->SignalDevicesChange.connect(this,
+                                             &CallClient::OnDevicesChange);
+  media_client_->set_secure(sdes_policy_);
+  media_client_->set_multisession_enabled(multisession_enabled_);
+}
+
+void CallClient::OnRequestSignaling() {
+  session_manager_->OnSignalingReady();
+}
+
+void CallClient::OnSessionCreate(cricket::Session* session, bool initiate) {
+  session->set_current_protocol(signaling_protocol_);
+}
+
+void CallClient::OnCallCreate(cricket::Call* call) {
+  call->SignalSessionState.connect(this, &CallClient::OnSessionState);
+  call->SignalMediaStreamsUpdate.connect(
+      this, &CallClient::OnMediaStreamsUpdate);
+}
+
+void CallClient::OnSessionState(cricket::Call* call,
+                                cricket::Session* session,
+                                cricket::Session::State state) {
+  if (state == cricket::Session::STATE_RECEIVEDINITIATE) {
+    buzz::Jid jid(session->remote_name());
+    if (call_ == call && multisession_enabled_) {
+      // We've received an initiate for an existing call. This is actually a
+      // new session for that call.
+      console_->PrintLine("Incoming session from '%s'", jid.Str().c_str());
+      AddSession(session);
+
+      cricket::CallOptions options;
+      options.has_video = call_->has_video();
+      options.data_channel_type = data_channel_type_;
+      call_->AcceptSession(session, options);
+
+      if (call_->has_video() && render_) {
+        RenderAllStreams(call, session, true);
+      }
+    } else {
+      console_->PrintLine("Incoming call from '%s'", jid.Str().c_str());
+      call_ = call;
+      AddSession(session);
+      incoming_call_ = true;
+      if (call->has_video() && render_) {
+        local_renderer_ =
+            cricket::VideoRendererFactory::CreateGuiVideoRenderer(160, 100);
+      }
+      if (auto_accept_) {
+        cricket::CallOptions options;
+        options.has_video = true;
+        options.data_channel_type = data_channel_type_;
+        Accept(options);
+      }
+    }
+  } else if (state == cricket::Session::STATE_SENTINITIATE) {
+    if (call->has_video() && render_) {
+      local_renderer_ =
+          cricket::VideoRendererFactory::CreateGuiVideoRenderer(160, 100);
+    }
+    console_->PrintLine("calling...");
+  } else if (state == cricket::Session::STATE_RECEIVEDACCEPT) {
+    console_->PrintLine("call answered");
+    SetupAcceptedCall();
+  } else if (state == cricket::Session::STATE_RECEIVEDREJECT) {
+    console_->PrintLine("call not answered");
+  } else if (state == cricket::Session::STATE_INPROGRESS) {
+    console_->PrintLine("call in progress");
+    call->SignalSpeakerMonitor.connect(this, &CallClient::OnSpeakerChanged);
+    call->StartSpeakerMonitor(session);
+  } else if (state == cricket::Session::STATE_RECEIVEDTERMINATE) {
+    console_->PrintLine("other side terminated");
+    TerminateAndRemoveSession(call, session->id());
+  }
+}
+
+void CallClient::OnSpeakerChanged(cricket::Call* call,
+                                  cricket::Session* session,
+                                  const cricket::StreamParams& speaker) {
+  if (!speaker.has_ssrcs()) {
+    console_->PrintLine("Session %s has no current speaker.",
+                        session->id().c_str());
+  } else if (speaker.id.empty()) {
+    console_->PrintLine("Session %s speaker change to unknown (%u).",
+                        session->id().c_str(), speaker.first_ssrc());
+  } else {
+    console_->PrintLine("Session %s speaker changed to %s (%u).",
+                        session->id().c_str(), speaker.id.c_str(),
+                        speaker.first_ssrc());
+  }
+}
+
+void SetMediaCaps(int media_caps, buzz::PresenceStatus* status) {
+  status->set_voice_capability((media_caps & cricket::AUDIO_RECV) != 0);
+  status->set_video_capability((media_caps & cricket::VIDEO_RECV) != 0);
+  status->set_camera_capability((media_caps & cricket::VIDEO_SEND) != 0);
+}
+
+void SetCaps(int media_caps, buzz::PresenceStatus* status) {
+  status->set_know_capabilities(true);
+  status->set_pmuc_capability(true);
+  SetMediaCaps(media_caps, status);
+}
+
+void SetAvailable(const buzz::Jid& jid, buzz::PresenceStatus* status) {
+  status->set_jid(jid);
+  status->set_available(true);
+  status->set_show(buzz::PresenceStatus::SHOW_ONLINE);
+}
+
+void CallClient::InitPresence() {
+  presence_push_ = new buzz::PresencePushTask(xmpp_client_, this);
+  presence_push_->SignalStatusUpdate.connect(
+    this, &CallClient::OnStatusUpdate);
+  presence_push_->SignalMucJoined.connect(this, &CallClient::OnMucJoined);
+  presence_push_->SignalMucLeft.connect(this, &CallClient::OnMucLeft);
+  presence_push_->SignalMucStatusUpdate.connect(
+    this, &CallClient::OnMucStatusUpdate);
+  presence_push_->Start();
+
+  presence_out_ = new buzz::PresenceOutTask(xmpp_client_);
+  SetAvailable(xmpp_client_->jid(), &my_status_);
+  SetCaps(media_client_->GetCapabilities(), &my_status_);
+  SendStatus(my_status_);
+  presence_out_->Start();
+
+  muc_invite_recv_ = new buzz::MucInviteRecvTask(xmpp_client_);
+  muc_invite_recv_->SignalInviteReceived.connect(this,
+      &CallClient::OnMucInviteReceived);
+  muc_invite_recv_->Start();
+
+  muc_invite_send_ = new buzz::MucInviteSendTask(xmpp_client_);
+  muc_invite_send_->Start();
+
+  friend_invite_send_ = new buzz::FriendInviteSendTask(xmpp_client_);
+  friend_invite_send_->Start();
+
+  StartXmppPing();
+}
+
+void CallClient::StartXmppPing() {
+  buzz::PingTask* ping = new buzz::PingTask(
+      xmpp_client_, rtc::Thread::Current(),
+      kPingPeriodMillis, kPingTimeoutMillis);
+  ping->SignalTimeout.connect(this, &CallClient::OnPingTimeout);
+  ping->Start();
+}
+
+void CallClient::OnPingTimeout() {
+  LOG(LS_WARNING) << "XMPP Ping timeout. Will keep trying...";
+  StartXmppPing();
+
+  // Or should we do this instead?
+  // Quit();
+}
+
+void CallClient::SendStatus(const buzz::PresenceStatus& status) {
+  presence_out_->Send(status);
+}
+
+void CallClient::OnStatusUpdate(const buzz::PresenceStatus& status) {
+  RosterItem item;
+  item.jid = status.jid();
+  item.show = status.show();
+  item.status = status.status();
+
+  std::string key = item.jid.Str();
+
+  if (status.available() && status.voice_capability()) {
+    if (show_roster_messages_) {
+      console_->PrintLine("Adding to roster: %s", key.c_str());
+    }
+    (*roster_)[key] = item;
+    // TODO: Make some of these constants.
+  } else {
+    if (show_roster_messages_) {
+      console_->PrintLine("Removing from roster: %s", key.c_str());
+    }
+    RosterMap::iterator iter = roster_->find(key);
+    if (iter != roster_->end())
+      roster_->erase(iter);
+  }
+}
+
+void CallClient::PrintRoster() {
+  console_->PrintLine("Roster contains %d callable", roster_->size());
+  RosterMap::iterator iter = roster_->begin();
+  while (iter != roster_->end()) {
+    console_->PrintLine("%s - %s",
+                        iter->second.jid.BareJid().Str().c_str(),
+                        DescribeStatus(iter->second.show, iter->second.status));
+    iter++;
+  }
+}
+
+void CallClient::SendChat(const std::string& to, const std::string msg) {
+  buzz::XmlElement* stanza = new buzz::XmlElement(buzz::QN_MESSAGE);
+  stanza->AddAttr(buzz::QN_TO, to);
+  stanza->AddAttr(buzz::QN_ID, rtc::CreateRandomString(16));
+  stanza->AddAttr(buzz::QN_TYPE, "chat");
+  buzz::XmlElement* body = new buzz::XmlElement(buzz::QN_BODY);
+  body->SetBodyText(msg);
+  stanza->AddElement(body);
+
+  xmpp_client_->SendStanza(stanza);
+  delete stanza;
+}
+
+void CallClient::SendData(const std::string& streamid,
+                          const std::string& text) {
+  // TODO(mylesj): Support sending data over sessions other than the first.
+  cricket::Session* session = GetFirstSession();
+  if (!call_ || !session) {
+    console_->PrintLine("Must be in a call to send data.");
+    return;
+  }
+  if (!call_->has_data()) {
+    console_->PrintLine("This call doesn't have a data channel.");
+    return;
+  }
+
+  const cricket::DataContentDescription* data =
+      cricket::GetFirstDataContentDescription(session->local_description());
+  if (!data) {
+    console_->PrintLine("This call doesn't have a data content.");
+    return;
+  }
+
+  cricket::StreamParams stream;
+  if (!cricket::GetStreamByIds(
+          data->streams(), "", streamid, &stream)) {
+    LOG(LS_WARNING) << "Could not send data: no such stream: "
+                    << streamid << ".";
+    return;
+  }
+
+  cricket::SendDataParams params;
+  params.ssrc = stream.first_ssrc();
+  rtc::Buffer payload(text.data(), text.length());
+  cricket::SendDataResult result;
+  bool sent = call_->SendData(session, params, payload, &result);
+  if (!sent) {
+    if (result == cricket::SDR_BLOCK) {
+      LOG(LS_WARNING) << "Could not send data because it would block.";
+    } else {
+      LOG(LS_WARNING) << "Could not send data for unknown reason.";
+    }
+  }
+}
+
+void CallClient::InviteFriend(const std::string& name) {
+  buzz::Jid jid(name);
+  if (!jid.IsValid() || jid.node() == "") {
+    console_->PrintLine("Invalid JID. JIDs should be in the form user@domain.");
+    return;
+  }
+  // Note: for some reason the Buzz backend does not forward our presence
+  // subscription requests to the end user when that user is another call
+  // client as opposed to a Smurf user. Thus, in that scenario, you must
+  // run the friend command as the other user too to create the linkage
+  // (and you won't be notified to do so).
+  friend_invite_send_->Send(jid);
+  console_->PrintLine("Requesting to befriend %s.", name.c_str());
+}
+
+bool CallClient::FindJid(const std::string& name, buzz::Jid* found_jid,
+                         cricket::CallOptions* options) {
+  bool found = false;
+  options->is_muc = false;
+  buzz::Jid callto_jid(name);
+  if (name.length() == 0 && mucs_.size() > 0) {
+    // if no name, and in a MUC, establish audio with the MUC
+    *found_jid = mucs_.begin()->first;
+    found = true;
+    options->is_muc = true;
+  } else if (name[0] == '+') {
+    // if the first character is a +, assume it's a phone number
+    *found_jid = callto_jid;
+    found = true;
+  } else {
+    // otherwise, it's a friend
+    for (RosterMap::iterator iter = roster_->begin();
+         iter != roster_->end(); ++iter) {
+      if (iter->second.jid.BareEquals(callto_jid)) {
+        found = true;
+        *found_jid = iter->second.jid;
+        break;
+      }
+    }
+
+    if (!found) {
+      if (mucs_.count(callto_jid) == 1 &&
+          mucs_[callto_jid]->state() == buzz::Muc::MUC_JOINED) {
+        found = true;
+        *found_jid = callto_jid;
+        options->is_muc = true;
+      }
+    }
+  }
+
+  if (found) {
+    console_->PrintLine("Found %s '%s'",
+                        options->is_muc ? "room" : "online friend",
+                        found_jid->Str().c_str());
+  } else {
+    console_->PrintLine("Could not find online friend '%s'", name.c_str());
+  }
+
+  return found;
+}
+
+void CallClient::OnDataReceived(cricket::Call*,
+                                const cricket::ReceiveDataParams& params,
+                                const rtc::Buffer& payload) {
+  // TODO(mylesj): Support receiving data on sessions other than the first.
+  cricket::Session* session = GetFirstSession();
+  if (!session)
+    return;
+
+  cricket::StreamParams stream;
+  const std::vector<cricket::StreamParams>* data_streams =
+      call_->GetDataRecvStreams(session);
+  std::string text(payload.data(), payload.length());
+  if (data_streams && GetStreamBySsrc(*data_streams, params.ssrc, &stream)) {
+    console_->PrintLine(
+        "Received data from '%s' on stream '%s' (ssrc=%u): %s",
+        stream.groupid.c_str(), stream.id.c_str(),
+        params.ssrc, text.c_str());
+  } else {
+    console_->PrintLine(
+        "Received data (ssrc=%u): %s",
+        params.ssrc, text.c_str());
+  }
+}
+
+bool CallClient::PlaceCall(const std::string& name,
+                           cricket::CallOptions options) {
+  buzz::Jid jid;
+  if (!FindJid(name, &jid, &options))
+    return false;
+
+  if (!call_) {
+    call_ = media_client_->CreateCall();
+    AddSession(call_->InitiateSession(jid, media_client_->jid(), options));
+  }
+  media_client_->SetFocus(call_);
+  if (call_->has_video() && render_ && !options.is_muc) {
+    // TODO(pthatcher): Hookup local_render_ to the local capturer.
+  }
+  if (options.is_muc) {
+    const std::string& nick = mucs_[jid]->local_jid().resource();
+    hangout_pubsub_client_ =
+        new buzz::HangoutPubSubClient(xmpp_client_, jid, nick);
+    hangout_pubsub_client_->SignalPresenterStateChange.connect(
+        this, &CallClient::OnPresenterStateChange);
+    hangout_pubsub_client_->SignalAudioMuteStateChange.connect(
+        this, &CallClient::OnAudioMuteStateChange);
+    hangout_pubsub_client_->SignalRecordingStateChange.connect(
+        this, &CallClient::OnRecordingStateChange);
+    hangout_pubsub_client_->SignalRemoteMute.connect(
+        this, &CallClient::OnRemoteMuted);
+    hangout_pubsub_client_->SignalMediaBlock.connect(
+        this, &CallClient::OnMediaBlocked);
+    hangout_pubsub_client_->SignalRequestError.connect(
+        this, &CallClient::OnHangoutRequestError);
+    hangout_pubsub_client_->SignalPublishAudioMuteError.connect(
+        this, &CallClient::OnHangoutPublishAudioMuteError);
+    hangout_pubsub_client_->SignalPublishPresenterError.connect(
+        this, &CallClient::OnHangoutPublishPresenterError);
+    hangout_pubsub_client_->SignalPublishRecordingError.connect(
+        this, &CallClient::OnHangoutPublishRecordingError);
+    hangout_pubsub_client_->SignalRemoteMuteError.connect(
+        this, &CallClient::OnHangoutRemoteMuteError);
+    hangout_pubsub_client_->RequestAll();
+  }
+
+  return true;
+}
+
+bool CallClient::InitiateAdditionalSession(const std::string& name,
+                                           cricket::CallOptions options) {
+  // Can't add a session if there is no call yet.
+  if (!call_)
+    return false;
+
+  buzz::Jid jid;
+  if (!FindJid(name, &jid, &options))
+    return false;
+
+  std::vector<cricket::Session*>& call_sessions = sessions_[call_->id()];
+  call_sessions.push_back(
+      call_->InitiateSession(jid,
+                             buzz::Jid(call_sessions[0]->remote_name()),
+                             options));
+
+  return true;
+}
+
+void CallClient::TerminateAndRemoveSession(cricket::Call* call,
+                                           const std::string& id) {
+  std::vector<cricket::Session*>& call_sessions = sessions_[call->id()];
+  for (std::vector<cricket::Session*>::iterator iter = call_sessions.begin();
+       iter != call_sessions.end(); ++iter) {
+    if ((*iter)->id() == id) {
+      RenderAllStreams(call, *iter, false);
+      call_->TerminateSession(*iter);
+      call_sessions.erase(iter);
+      break;
+    }
+  }
+}
+
+void CallClient::PrintCalls() {
+  const std::map<uint32, cricket::Call*>& calls = media_client_->calls();
+  for (std::map<uint32, cricket::Call*>::const_iterator i = calls.begin();
+       i != calls.end(); ++i) {
+    console_->PrintLine("Call (id:%d), is %s",
+                        i->first,
+                        i->second == call_ ? "active" : "on hold");
+    std::vector<cricket::Session *>& sessions = sessions_[call_->id()];
+    for (std::vector<cricket::Session *>::const_iterator j = sessions.begin();
+         j != sessions.end(); ++j) {
+      console_->PrintLine("|--Session (id:%s), to %s", (*j)->id().c_str(),
+                          (*j)->remote_name().c_str());
+
+      std::vector<cricket::StreamParams>::const_iterator k;
+      const std::vector<cricket::StreamParams>* streams =
+          i->second->GetAudioRecvStreams(*j);
+      if (streams)
+        for (k = streams->begin(); k != streams->end(); ++k) {
+          console_->PrintLine("|----Audio Stream: %s", k->ToString().c_str());
+        }
+      streams = i->second->GetVideoRecvStreams(*j);
+      if (streams)
+        for (k = streams->begin(); k != streams->end(); ++k) {
+          console_->PrintLine("|----Video Stream: %s", k->ToString().c_str());
+        }
+      streams = i->second->GetDataRecvStreams(*j);
+      if (streams)
+        for (k = streams->begin(); k != streams->end(); ++k) {
+          console_->PrintLine("|----Data Stream: %s", k->ToString().c_str());
+        }
+    }
+  }
+}
+
+void CallClient::SwitchToCall(uint32 call_id) {
+  const std::map<uint32, cricket::Call*>& calls = media_client_->calls();
+  std::map<uint32, cricket::Call*>::const_iterator call_iter =
+      calls.find(call_id);
+  if (call_iter != calls.end()) {
+    media_client_->SetFocus(call_iter->second);
+    call_ = call_iter->second;
+  } else {
+    console_->PrintLine("Unable to find call: %d", call_id);
+  }
+}
+
+void CallClient::OnPresenterStateChange(
+    const std::string& nick, bool was_presenting, bool is_presenting) {
+  if (!was_presenting && is_presenting) {
+    console_->PrintLine("%s now presenting.", nick.c_str());
+  } else if (was_presenting && !is_presenting) {
+    console_->PrintLine("%s no longer presenting.", nick.c_str());
+  } else if (was_presenting && is_presenting) {
+    console_->PrintLine("%s still presenting.", nick.c_str());
+  } else if (!was_presenting && !is_presenting) {
+    console_->PrintLine("%s still not presenting.", nick.c_str());
+  }
+}
+
+void CallClient::OnAudioMuteStateChange(
+    const std::string& nick, bool was_muted, bool is_muted) {
+  if (!was_muted && is_muted) {
+    console_->PrintLine("%s now muted.", nick.c_str());
+  } else if (was_muted && !is_muted) {
+    console_->PrintLine("%s no longer muted.", nick.c_str());
+  }
+}
+
+void CallClient::OnRecordingStateChange(
+    const std::string& nick, bool was_recording, bool is_recording) {
+  if (!was_recording && is_recording) {
+    console_->PrintLine("%s now recording.", nick.c_str());
+  } else if (was_recording && !is_recording) {
+    console_->PrintLine("%s no longer recording.", nick.c_str());
+  }
+}
+
+void CallClient::OnRemoteMuted(const std::string& mutee_nick,
+                               const std::string& muter_nick,
+                               bool should_mute_locally) {
+  if (should_mute_locally) {
+    call_->Mute(true);
+    console_->PrintLine("Remote muted by %s.", muter_nick.c_str());
+  } else {
+    console_->PrintLine("%s remote muted by %s.",
+                        mutee_nick.c_str(), muter_nick.c_str());
+  }
+}
+
+void CallClient::OnMediaBlocked(const std::string& blockee_nick,
+                                const std::string& blocker_nick) {
+  console_->PrintLine("%s blocked by %s.",
+                      blockee_nick.c_str(), blocker_nick.c_str());
+}
+
+void CallClient::OnHangoutRequestError(const std::string& node,
+                                       const buzz::XmlElement* stanza) {
+  console_->PrintLine("Failed request pub sub items for node %s.",
+                      node.c_str());
+}
+
+void CallClient::OnHangoutPublishAudioMuteError(
+    const std::string& task_id, const buzz::XmlElement* stanza) {
+  console_->PrintLine("Failed to publish audio mute state.");
+}
+
+void CallClient::OnHangoutPublishPresenterError(
+    const std::string& task_id, const buzz::XmlElement* stanza) {
+  console_->PrintLine("Failed to publish presenting state.");
+}
+
+void CallClient::OnHangoutPublishRecordingError(
+    const std::string& task_id, const buzz::XmlElement* stanza) {
+  console_->PrintLine("Failed to publish recording state.");
+}
+
+void CallClient::OnHangoutRemoteMuteError(const std::string& task_id,
+                                          const std::string& mutee_nick,
+                                          const buzz::XmlElement* stanza) {
+  console_->PrintLine("Failed to remote mute.");
+}
+
+void CallClient::Accept(const cricket::CallOptions& options) {
+  ASSERT(call_ && incoming_call_);
+  ASSERT(sessions_[call_->id()].size() == 1);
+  cricket::Session* session = GetFirstSession();
+  call_->AcceptSession(session, options);
+  media_client_->SetFocus(call_);
+  if (call_->has_video() && render_) {
+    // TODO(pthatcher): Hookup local_render_ to the local capturer.
+    RenderAllStreams(call_, session, true);
+  }
+  SetupAcceptedCall();
+  incoming_call_ = false;
+}
+
+void CallClient::SetupAcceptedCall() {
+  if (call_->has_data()) {
+    call_->SignalDataReceived.connect(this, &CallClient::OnDataReceived);
+  }
+}
+
+void CallClient::Reject() {
+  ASSERT(call_ && incoming_call_);
+  call_->RejectSession(call_->sessions()[0]);
+  incoming_call_ = false;
+}
+
+void CallClient::Quit() {
+  rtc::Thread::Current()->Quit();
+}
+
+void CallClient::SetNick(const std::string& muc_nick) {
+  my_status_.set_nick(muc_nick);
+
+  // TODO: We might want to re-send presence, but right
+  // now, it appears to be ignored by the MUC.
+  //
+  // presence_out_->Send(my_status_); for (MucMap::const_iterator itr
+  // = mucs_.begin(); itr != mucs_.end(); ++itr) {
+  // presence_out_->SendDirected(itr->second->local_jid(),
+  // my_status_); }
+
+  console_->PrintLine("Nick set to '%s'.", muc_nick.c_str());
+}
+
+void CallClient::LookupAndJoinMuc(const std::string& room_name) {
+  // The room_name can't be empty for lookup task.
+  if (room_name.empty()) {
+    console_->PrintLine("Please provide a room name or room jid.");
+    return;
+  }
+
+  std::string room = room_name;
+  std::string domain = xmpp_client_->jid().domain();
+  if (room_name.find("@") != std::string::npos) {
+    // Assume the room_name is a fully qualified room name.
+    // We'll find the room name string and domain name string from it.
+    room = room_name.substr(0, room_name.find("@"));
+    domain = room_name.substr(room_name.find("@") + 1);
+  }
+
+  buzz::MucRoomLookupTask* lookup_query_task =
+      buzz::MucRoomLookupTask::CreateLookupTaskForRoomName(
+          xmpp_client_, buzz::Jid(buzz::STR_GOOGLE_MUC_LOOKUP_JID), room,
+          domain);
+  lookup_query_task->SignalResult.connect(this,
+      &CallClient::OnRoomLookupResponse);
+  lookup_query_task->SignalError.connect(this,
+      &CallClient::OnRoomLookupError);
+  lookup_query_task->Start();
+}
+
+void CallClient::JoinMuc(const std::string& room_jid_str) {
+  if (room_jid_str.empty()) {
+    buzz::Jid room_jid = GenerateRandomMucJid();
+    console_->PrintLine("Generated a random room jid: %s",
+                        room_jid.Str().c_str());
+    JoinMuc(room_jid);
+  } else {
+    JoinMuc(buzz::Jid(room_jid_str));
+  }
+}
+
+void CallClient::JoinMuc(const buzz::Jid& room_jid) {
+  if (!room_jid.IsValid()) {
+    console_->PrintLine("Unable to make valid muc endpoint for %s",
+                        room_jid.Str().c_str());
+    return;
+  }
+
+  std::string room_nick = room_jid.resource();
+  if (room_nick.empty()) {
+    room_nick = (xmpp_client_->jid().node()
+                 + "_" + xmpp_client_->jid().resource());
+  }
+
+  MucMap::iterator elem = mucs_.find(room_jid);
+  if (elem != mucs_.end()) {
+    console_->PrintLine("This MUC already exists.");
+    return;
+  }
+
+  buzz::Muc* muc = new buzz::Muc(room_jid.BareJid(), room_nick);
+  mucs_[muc->jid()] = muc;
+  presence_out_->SendDirected(muc->local_jid(), my_status_);
+}
+
+void CallClient::OnRoomLookupResponse(buzz::MucRoomLookupTask* task,
+                                      const buzz::MucRoomInfo& room) {
+  // The server requires the room be "configured" before being used.
+  // We only need to configure it if we create it, but rooms are
+  // auto-created at lookup, so there's currently no way to know if we
+  // created it.  So, we configure it every time, just in case.
+  // Luckily, it appears to be safe to configure a room that's already
+  // configured.  Our current flow is:
+  // 1. Lookup/auto-create
+  // 2. Configure
+  // 3. Join
+  // TODO: In the future, once the server supports it, we
+  // should:
+  // 1. Lookup
+  // 2. Create and Configure if necessary
+  // 3. Join
+  std::vector<std::string> room_features;
+  room_features.push_back(buzz::STR_MUC_ROOM_FEATURE_ENTERPRISE);
+  buzz::MucRoomConfigTask* room_config_task = new buzz::MucRoomConfigTask(
+      xmpp_client_, room.jid, room.full_name(), room_features);
+  room_config_task->SignalResult.connect(this,
+      &CallClient::OnRoomConfigResult);
+  room_config_task->SignalError.connect(this,
+      &CallClient::OnRoomConfigError);
+  room_config_task->Start();
+}
+
+void CallClient::OnRoomLookupError(buzz::IqTask* task,
+                                   const buzz::XmlElement* stanza) {
+  if (stanza == NULL) {
+    console_->PrintLine("Room lookup failed.");
+  } else {
+    console_->PrintLine("Room lookup error: ", stanza->Str().c_str());
+  }
+}
+
+void CallClient::OnRoomConfigResult(buzz::MucRoomConfigTask* task) {
+  JoinMuc(task->room_jid());
+}
+
+void CallClient::OnRoomConfigError(buzz::IqTask* task,
+                                   const buzz::XmlElement* stanza) {
+  console_->PrintLine("Room config failed.");
+  // We join the muc anyway, because if the room is already
+  // configured, the configure will fail, but we still want to join.
+  // Idealy, we'd know why the room config failed and only do this on
+  // "already configured" errors.  But right now all we get back is
+  // "not-allowed".
+  buzz::MucRoomConfigTask* config_task =
+      static_cast<buzz::MucRoomConfigTask*>(task);
+  JoinMuc(config_task->room_jid());
+}
+
+void CallClient::OnMucInviteReceived(const buzz::Jid& inviter,
+    const buzz::Jid& room,
+    const std::vector<buzz::AvailableMediaEntry>& avail) {
+
+  console_->PrintLine("Invited to join %s by %s.", room.Str().c_str(),
+      inviter.Str().c_str());
+  console_->PrintLine("Available media:");
+  if (avail.size() > 0) {
+    for (std::vector<buzz::AvailableMediaEntry>::const_iterator i =
+            avail.begin();
+        i != avail.end();
+        ++i) {
+      console_->PrintLine("  %s, %s",
+                          buzz::AvailableMediaEntry::TypeAsString(i->type),
+                          buzz::AvailableMediaEntry::StatusAsString(i->status));
+    }
+  } else {
+    console_->PrintLine("  None");
+  }
+  // We automatically join the room.
+  JoinMuc(room);
+}
+
+void CallClient::OnMucJoined(const buzz::Jid& endpoint) {
+  MucMap::iterator elem = mucs_.find(endpoint);
+  ASSERT(elem != mucs_.end() &&
+         elem->second->state() == buzz::Muc::MUC_JOINING);
+
+  buzz::Muc* muc = elem->second;
+  muc->set_state(buzz::Muc::MUC_JOINED);
+  console_->PrintLine("Joined \"%s\"", muc->jid().Str().c_str());
+}
+
+void CallClient::OnMucStatusUpdate(const buzz::Jid& jid,
+    const buzz::MucPresenceStatus& status) {
+
+  // Look up this muc.
+  MucMap::iterator elem = mucs_.find(jid);
+  ASSERT(elem != mucs_.end());
+
+  buzz::Muc* muc = elem->second;
+
+  if (status.jid().IsBare() || status.jid() == muc->local_jid()) {
+    // We are only interested in status about other users.
+    return;
+  }
+
+  if (status.available()) {
+    muc->members()[status.jid().resource()] = status;
+  } else {
+    muc->members().erase(status.jid().resource());
+  }
+}
+
+bool CallClient::InMuc() {
+  const buzz::Jid* muc_jid = FirstMucJid();
+  if (!muc_jid) return false;
+  return muc_jid->IsValid();
+}
+
+const buzz::Jid* CallClient::FirstMucJid() {
+  if (mucs_.empty()) return NULL;
+  return &(mucs_.begin()->first);
+}
+
+void CallClient::LeaveMuc(const std::string& room) {
+  buzz::Jid room_jid;
+  const buzz::Jid* muc_jid = FirstMucJid();
+  if (room.length() > 0) {
+    room_jid = buzz::Jid(room);
+  } else if (mucs_.size() > 0) {
+    // leave the first MUC if no JID specified
+    if (muc_jid) {
+      room_jid = *(muc_jid);
+    }
+  }
+
+  if (!room_jid.IsValid()) {
+    console_->PrintLine("Invalid MUC JID.");
+    return;
+  }
+
+  MucMap::iterator elem = mucs_.find(room_jid);
+  if (elem == mucs_.end()) {
+    console_->PrintLine("No such MUC.");
+    return;
+  }
+
+  buzz::Muc* muc = elem->second;
+  muc->set_state(buzz::Muc::MUC_LEAVING);
+
+  buzz::PresenceStatus status;
+  status.set_jid(my_status_.jid());
+  status.set_available(false);
+  status.set_priority(0);
+  presence_out_->SendDirected(muc->local_jid(), status);
+}
+
+void CallClient::OnMucLeft(const buzz::Jid& endpoint, int error) {
+  // We could be kicked from a room from any state.  We would hope this
+  // happens While in the MUC_LEAVING state
+  MucMap::iterator elem = mucs_.find(endpoint);
+  if (elem == mucs_.end())
+    return;
+
+  buzz::Muc* muc = elem->second;
+  if (muc->state() == buzz::Muc::MUC_JOINING) {
+    console_->PrintLine("Failed to join \"%s\", code=%d",
+                        muc->jid().Str().c_str(), error);
+  } else if (muc->state() == buzz::Muc::MUC_JOINED) {
+    console_->PrintLine("Kicked from \"%s\"",
+                        muc->jid().Str().c_str());
+  }
+
+  delete muc;
+  mucs_.erase(elem);
+}
+
+void CallClient::InviteToMuc(const std::string& given_user,
+                             const std::string& room) {
+  std::string user = given_user;
+
+  // First find the room.
+  const buzz::Muc* found_muc;
+  if (room.length() == 0) {
+    if (mucs_.size() == 0) {
+      console_->PrintLine("Not in a room yet; can't invite.");
+      return;
+    }
+    // Invite to the first muc
+    found_muc = mucs_.begin()->second;
+  } else {
+    MucMap::iterator elem = mucs_.find(buzz::Jid(room));
+    if (elem == mucs_.end()) {
+      console_->PrintLine("Not in room %s.", room.c_str());
+      return;
+    }
+    found_muc = elem->second;
+  }
+
+  buzz::Jid invite_to = found_muc->jid();
+
+  // Now find the user. We invite all of their resources.
+  bool found_user = false;
+  buzz::Jid user_jid(user);
+  for (RosterMap::iterator iter = roster_->begin();
+       iter != roster_->end(); ++iter) {
+    if (iter->second.jid.BareEquals(user_jid)) {
+      buzz::Jid invitee = iter->second.jid;
+      muc_invite_send_->Send(invite_to, invitee);
+      found_user = true;
+    }
+  }
+  if (!found_user) {
+    buzz::Jid invitee = user_jid;
+    muc_invite_send_->Send(invite_to, invitee);
+  }
+}
+
+void CallClient::GetDevices() {
+  std::vector<std::string> names;
+  media_client_->GetAudioInputDevices(&names);
+  console_->PrintLine("Audio input devices:");
+  PrintDevices(names);
+  media_client_->GetAudioOutputDevices(&names);
+  console_->PrintLine("Audio output devices:");
+  PrintDevices(names);
+  media_client_->GetVideoCaptureDevices(&names);
+  console_->PrintLine("Video capture devices:");
+  PrintDevices(names);
+}
+
+void CallClient::PrintDevices(const std::vector<std::string>& names) {
+  for (size_t i = 0; i < names.size(); ++i) {
+    console_->PrintLine("%d: %s", static_cast<int>(i), names[i].c_str());
+  }
+}
+
+void CallClient::OnDevicesChange() {
+  console_->PrintLine("Devices changed.");
+  SetMediaCaps(media_client_->GetCapabilities(), &my_status_);
+  SendStatus(my_status_);
+}
+
+void CallClient::SetVolume(const std::string& level) {
+  media_client_->SetOutputVolume(strtol(level.c_str(), NULL, 10));
+}
+
+void CallClient::OnMediaStreamsUpdate(cricket::Call* call,
+                                      cricket::Session* session,
+                                      const cricket::MediaStreams& added,
+                                      const cricket::MediaStreams& removed) {
+  if (call && call->has_video()) {
+    for (std::vector<cricket::StreamParams>::const_iterator
+         it = removed.video().begin(); it != removed.video().end(); ++it) {
+      RemoveStaticRenderedView(it->first_ssrc());
+    }
+
+    if (render_) {
+      RenderStreams(call, session, added.video(), true);
+    }
+    SendViewRequest(call, session);
+  }
+}
+
+void CallClient::RenderAllStreams(cricket::Call* call,
+                                  cricket::Session* session,
+                                  bool enable) {
+  const std::vector<cricket::StreamParams>* video_streams =
+      call->GetVideoRecvStreams(session);
+  if (video_streams) {
+    RenderStreams(call, session, *video_streams, enable);
+  }
+}
+
+void CallClient::RenderStreams(
+    cricket::Call* call,
+    cricket::Session* session,
+    const std::vector<cricket::StreamParams>& video_streams,
+    bool enable) {
+  std::vector<cricket::StreamParams>::const_iterator stream;
+  for (stream = video_streams.begin(); stream != video_streams.end();
+       ++stream) {
+    RenderStream(call, session, *stream, enable);
+  }
+}
+
+void CallClient::RenderStream(cricket::Call* call,
+                              cricket::Session* session,
+                              const cricket::StreamParams& stream,
+                              bool enable) {
+  if (!stream.has_ssrcs()) {
+    // Nothing to see here; move along.
+    return;
+  }
+
+  uint32 ssrc = stream.first_ssrc();
+  StaticRenderedViews::iterator iter =
+      static_rendered_views_.find(std::make_pair(session, ssrc));
+  if (enable) {
+    if (iter == static_rendered_views_.end()) {
+      // TODO(pthatcher): Make dimensions and positions more configurable.
+      int offset = (50 * static_views_accumulated_count_) % 300;
+      AddStaticRenderedView(session, ssrc, 640, 400, 30,
+                            offset, offset);
+      // Should have it now.
+      iter = static_rendered_views_.find(std::make_pair(session, ssrc));
+    }
+    call->SetVideoRenderer(session, ssrc, iter->second.renderer);
+  } else {
+    if (iter != static_rendered_views_.end()) {
+      call->SetVideoRenderer(session, ssrc, NULL);
+      RemoveStaticRenderedView(ssrc);
+    }
+  }
+}
+
+// TODO: Would these methods to add and remove views make
+// more sense in call.cc?  Would other clients use them?
+void CallClient::AddStaticRenderedView(
+    cricket::Session* session,
+    uint32 ssrc, int width, int height, int framerate,
+    int x_offset, int y_offset) {
+  StaticRenderedView rendered_view(
+      cricket::StaticVideoView(
+          cricket::StreamSelector(ssrc), width, height, framerate),
+      cricket::VideoRendererFactory::CreateGuiVideoRenderer(
+          x_offset, y_offset));
+  rendered_view.renderer->SetSize(width, height, 0);
+  static_rendered_views_.insert(std::make_pair(std::make_pair(session, ssrc),
+                                               rendered_view));
+  ++static_views_accumulated_count_;
+  console_->PrintLine("Added renderer for ssrc %d", ssrc);
+}
+
+bool CallClient::RemoveStaticRenderedView(uint32 ssrc) {
+  for (StaticRenderedViews::iterator it = static_rendered_views_.begin();
+       it != static_rendered_views_.end(); ++it) {
+    if (it->second.view.selector.ssrc == ssrc) {
+      delete it->second.renderer;
+      static_rendered_views_.erase(it);
+      console_->PrintLine("Removed renderer for ssrc %d", ssrc);
+      return true;
+    }
+  }
+  return false;
+}
+
+void CallClient::RemoveCallsStaticRenderedViews(cricket::Call* call) {
+  std::vector<cricket::Session*>& sessions = sessions_[call->id()];
+  std::set<cricket::Session*> call_sessions(sessions.begin(), sessions.end());
+  for (StaticRenderedViews::iterator it = static_rendered_views_.begin();
+       it != static_rendered_views_.end(); ) {
+    if (call_sessions.find(it->first.first) != call_sessions.end()) {
+      delete it->second.renderer;
+      static_rendered_views_.erase(it++);
+    } else {
+      ++it;
+    }
+  }
+}
+
+void CallClient::SendViewRequest(cricket::Call* call,
+                                 cricket::Session* session) {
+  cricket::ViewRequest request;
+  for (StaticRenderedViews::iterator it = static_rendered_views_.begin();
+       it != static_rendered_views_.end(); ++it) {
+    if (it->first.first == session) {
+      request.static_video_views.push_back(it->second.view);
+    }
+  }
+  call->SendViewRequest(session, request);
+}
+
+buzz::Jid CallClient::GenerateRandomMucJid() {
+  // Generate a GUID of the form XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX,
+  // for an eventual JID of private-chat-<GUID>@groupchat.google.com.
+  char guid[37], guid_room[256];
+  for (size_t i = 0; i < ARRAY_SIZE(guid) - 1;) {
+    if (i == 8 || i == 13 || i == 18 || i == 23) {
+      guid[i++] = '-';
+    } else {
+      sprintf(guid + i, "%04x", rand());
+      i += 4;
+    }
+  }
+
+  rtc::sprintfn(guid_room,
+                      ARRAY_SIZE(guid_room),
+                      "private-chat-%s@%s",
+                      guid,
+                      pmuc_domain_.c_str());
+  return buzz::Jid(guid_room);
+}
+
+bool CallClient::SelectFirstDesktopScreencastId(
+    cricket::ScreencastId* screencastid) {
+  if (!rtc::WindowPickerFactory::IsSupported()) {
+    LOG(LS_WARNING) << "Window picker not suported on this OS.";
+    return false;
+  }
+
+  rtc::WindowPicker* picker =
+      rtc::WindowPickerFactory::CreateWindowPicker();
+  if (!picker) {
+    LOG(LS_WARNING) << "Could not create a window picker.";
+    return false;
+  }
+
+  rtc::DesktopDescriptionList desktops;
+  if (!picker->GetDesktopList(&desktops) || desktops.empty()) {
+    LOG(LS_WARNING) << "Could not get a list of desktops.";
+    return false;
+  }
+
+  *screencastid = cricket::ScreencastId(desktops[0].id());
+  return true;
+}
+
+void CallClient::PrintStats() const {
+  const cricket::VoiceMediaInfo& vmi = call_->last_voice_media_info();
+
+  for (std::vector<cricket::VoiceSenderInfo>::const_iterator it =
+       vmi.senders.begin(); it != vmi.senders.end(); ++it) {
+    console_->PrintLine("Sender: ssrc=%u codec='%s' bytes=%d packets=%d "
+                        "rtt=%d jitter=%d",
+                        it->ssrc(), it->codec_name.c_str(), it->bytes_sent,
+                        it->packets_sent, it->rtt_ms, it->jitter_ms);
+  }
+
+  for (std::vector<cricket::VoiceReceiverInfo>::const_iterator it =
+       vmi.receivers.begin(); it != vmi.receivers.end(); ++it) {
+    console_->PrintLine("Receiver: ssrc=%u bytes=%d packets=%d "
+                        "jitter=%d loss=%.2f",
+                        it->ssrc(), it->bytes_rcvd, it->packets_rcvd,
+                        it->jitter_ms, it->fraction_lost);
+  }
+}
diff --git a/libjingle/examples/call/callclient.h b/libjingle/examples/call/callclient.h
new file mode 100644
index 0000000..ac427b4
--- /dev/null
+++ b/libjingle/examples/call/callclient.h
@@ -0,0 +1,352 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, 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.
+ */
+
+#ifndef TALK_EXAMPLES_CALL_CALLCLIENT_H_
+#define TALK_EXAMPLES_CALL_CALLCLIENT_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "talk/examples/call/console.h"
+#include "talk/media/base/mediachannel.h"
+#include "webrtc/p2p/base/session.h"
+#include "webrtc/libjingle/media/mediamessages.h"
+#include "webrtc/libjingle/media/mediasessionclient.h"
+#include "webrtc/libjingle/xmpp/hangoutpubsubclient.h"
+#include "webrtc/libjingle/xmpp/presencestatus.h"
+#include "webrtc/libjingle/xmpp/xmppclient.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/base/sslidentity.h"
+
+namespace buzz {
+class PresencePushTask;
+class PresenceOutTask;
+class MucInviteRecvTask;
+class MucInviteSendTask;
+class FriendInviteSendTask;
+class DiscoInfoQueryTask;
+class Muc;
+class PresenceStatus;
+class IqTask;
+class MucRoomConfigTask;
+class MucRoomLookupTask;
+class MucPresenceStatus;
+class XmlElement;
+class HangoutPubSubClient;
+struct AvailableMediaEntry;
+struct MucRoomInfo;
+}  // namespace buzz
+
+namespace rtc {
+class Thread;
+class NetworkManager;
+}  // namespace rtc
+
+namespace cricket {
+class PortAllocator;
+class MediaEngineInterface;
+class MediaSessionClient;
+class Call;
+class SessionManagerTask;
+struct CallOptions;
+struct MediaStreams;
+struct StreamParams;
+}  // namespace cricket
+
+struct RosterItem {
+  buzz::Jid jid;
+  buzz::PresenceStatus::Show show;
+  std::string status;
+};
+
+struct StaticRenderedView {
+  StaticRenderedView(const cricket::StaticVideoView& view,
+                     cricket::VideoRenderer* renderer) :
+      view(view),
+      renderer(renderer) {
+  }
+
+  cricket::StaticVideoView view;
+  cricket::VideoRenderer* renderer;
+};
+
+// Maintain a mapping of (session, ssrc) to rendered view.
+typedef std::map<std::pair<cricket::Session*, uint32>,
+                 StaticRenderedView> StaticRenderedViews;
+
+class CallClient: public sigslot::has_slots<> {
+ public:
+  CallClient(buzz::XmppClient* xmpp_client,
+             const std::string& caps_node,
+             const std::string& version);
+  ~CallClient();
+
+  cricket::MediaSessionClient* media_client() const { return media_client_; }
+  void SetMediaEngine(cricket::MediaEngineInterface* media_engine) {
+    media_engine_ = media_engine;
+  }
+  void SetAutoAccept(bool auto_accept) {
+    auto_accept_ = auto_accept;
+  }
+  void SetPmucDomain(const std::string &pmuc_domain) {
+    pmuc_domain_ = pmuc_domain;
+  }
+  void SetRender(bool render) {
+    render_ = render;
+  }
+  void SetDataChannelType(cricket::DataChannelType data_channel_type) {
+    data_channel_type_ = data_channel_type;
+  }
+  void SetMultiSessionEnabled(bool multisession_enabled) {
+    multisession_enabled_ = multisession_enabled;
+  }
+  void SetConsole(Console *console) {
+    console_ = console;
+  }
+  void SetPriority(int priority) {
+    my_status_.set_priority(priority);
+  }
+  void SendStatus() {
+    SendStatus(my_status_);
+  }
+  void SendStatus(const buzz::PresenceStatus& status);
+
+  void ParseLine(const std::string &str);
+
+  void SendChat(const std::string& to, const std::string msg);
+  void SendData(const std::string& stream_name,
+                const std::string& text);
+  void InviteFriend(const std::string& user);
+  void JoinMuc(const buzz::Jid& room_jid);
+  void JoinMuc(const std::string& room_jid_str);
+  void LookupAndJoinMuc(const std::string& room_name);
+  void InviteToMuc(const std::string& user, const std::string& room);
+  bool InMuc();
+  const buzz::Jid* FirstMucJid();
+  void LeaveMuc(const std::string& room);
+  void SetNick(const std::string& muc_nick);
+  void SetPortAllocatorFlags(uint32 flags) { portallocator_flags_ = flags; }
+  void SetAllowLocalIps(bool allow_local_ips) {
+    allow_local_ips_ = allow_local_ips;
+  }
+
+  void SetSignalingProtocol(cricket::SignalingProtocol protocol) {
+    signaling_protocol_ = protocol;
+  }
+  void SetTransportProtocol(cricket::TransportProtocol protocol) {
+    transport_protocol_ = protocol;
+  }
+  void SetSecurePolicy(cricket::SecurePolicy sdes_policy,
+                       cricket::SecurePolicy dtls_policy) {
+    sdes_policy_ = sdes_policy;
+    dtls_policy_ = dtls_policy;
+  }
+  void SetSslIdentity(rtc::SSLIdentity* identity) {
+    ssl_identity_.reset(identity);
+  }
+
+  typedef std::map<buzz::Jid, buzz::Muc*> MucMap;
+
+  const MucMap& mucs() const {
+    return mucs_;
+  }
+
+  void SetShowRosterMessages(bool show_roster_messages) {
+    show_roster_messages_ = show_roster_messages;
+  }
+
+ private:
+  void AddStream(uint32 audio_src_id, uint32 video_src_id);
+  void RemoveStream(uint32 audio_src_id, uint32 video_src_id);
+  void OnStateChange(buzz::XmppEngine::State state);
+
+  void InitMedia();
+  void InitPresence();
+  void StartXmppPing();
+  void OnPingTimeout();
+  void OnRequestSignaling();
+  void OnSessionCreate(cricket::Session* session, bool initiate);
+  void OnCallCreate(cricket::Call* call);
+  void OnCallDestroy(cricket::Call* call);
+  void OnSessionState(cricket::Call* call,
+                      cricket::Session* session,
+                      cricket::Session::State state);
+  void OnStatusUpdate(const buzz::PresenceStatus& status);
+  void OnMucInviteReceived(const buzz::Jid& inviter, const buzz::Jid& room,
+      const std::vector<buzz::AvailableMediaEntry>& avail);
+  void OnMucJoined(const buzz::Jid& endpoint);
+  void OnMucStatusUpdate(const buzz::Jid& jid,
+                         const buzz::MucPresenceStatus& status);
+  void OnMucLeft(const buzz::Jid& endpoint, int error);
+  void OnPresenterStateChange(const std::string& nick,
+                              bool was_presenting, bool is_presenting);
+  void OnAudioMuteStateChange(const std::string& nick,
+                              bool was_muted, bool is_muted);
+  void OnRecordingStateChange(const std::string& nick,
+                              bool was_recording, bool is_recording);
+  void OnRemoteMuted(const std::string& mutee_nick,
+                     const std::string& muter_nick,
+                     bool should_mute_locally);
+  void OnMediaBlocked(const std::string& blockee_nick,
+                      const std::string& blocker_nick);
+  void OnHangoutRequestError(const std::string& node,
+                             const buzz::XmlElement* stanza);
+  void OnHangoutPublishAudioMuteError(const std::string& task_id,
+                                      const buzz::XmlElement* stanza);
+  void OnHangoutPublishPresenterError(const std::string& task_id,
+                                      const buzz::XmlElement* stanza);
+  void OnHangoutPublishRecordingError(const std::string& task_id,
+                                      const buzz::XmlElement* stanza);
+  void OnHangoutRemoteMuteError(const std::string& task_id,
+                                const std::string& mutee_nick,
+                                const buzz::XmlElement* stanza);
+  void OnDevicesChange();
+  void OnMediaStreamsUpdate(cricket::Call* call,
+                            cricket::Session* session,
+                            const cricket::MediaStreams& added,
+                            const cricket::MediaStreams& removed);
+  void OnSpeakerChanged(cricket::Call* call,
+                        cricket::Session* session,
+                        const cricket::StreamParams& speaker_stream);
+  void OnRoomLookupResponse(buzz::MucRoomLookupTask* task,
+                            const buzz::MucRoomInfo& room_info);
+  void OnRoomLookupError(buzz::IqTask* task,
+                         const buzz::XmlElement* stanza);
+  void OnRoomConfigResult(buzz::MucRoomConfigTask* task);
+  void OnRoomConfigError(buzz::IqTask* task,
+                         const buzz::XmlElement* stanza);
+  void OnDataReceived(cricket::Call*,
+                      const cricket::ReceiveDataParams& params,
+                      const rtc::Buffer& payload);
+  buzz::Jid GenerateRandomMucJid();
+
+  // Depending on |enable|, render (or don't) all the streams in |session|.
+  void RenderAllStreams(cricket::Call* call,
+                        cricket::Session* session,
+                        bool enable);
+
+  // Depending on |enable|, render (or don't) the streams in |video_streams|.
+  void RenderStreams(cricket::Call* call,
+                     cricket::Session* session,
+                     const std::vector<cricket::StreamParams>& video_streams,
+                     bool enable);
+
+  // Depending on |enable|, render (or don't) the supplied |stream|.
+  void RenderStream(cricket::Call* call,
+                    cricket::Session* session,
+                    const cricket::StreamParams& stream,
+                    bool enable);
+  void AddStaticRenderedView(
+      cricket::Session* session,
+      uint32 ssrc, int width, int height, int framerate,
+      int x_offset, int y_offset);
+  bool RemoveStaticRenderedView(uint32 ssrc);
+  void RemoveCallsStaticRenderedViews(cricket::Call* call);
+  void SendViewRequest(cricket::Call* call, cricket::Session* session);
+  bool SelectFirstDesktopScreencastId(cricket::ScreencastId* screencastid);
+
+  static const std::string strerror(buzz::XmppEngine::Error err);
+
+  void PrintRoster();
+  bool FindJid(const std::string& name,
+               buzz::Jid* found_jid,
+               cricket::CallOptions* options);
+  bool PlaceCall(const std::string& name, cricket::CallOptions options);
+  bool InitiateAdditionalSession(const std::string& name,
+                                 cricket::CallOptions options);
+  void TerminateAndRemoveSession(cricket::Call* call, const std::string& id);
+  void PrintCalls();
+  void SwitchToCall(uint32 call_id);
+  void Accept(const cricket::CallOptions& options);
+  void Reject();
+  void Quit();
+
+  void GetDevices();
+  void PrintDevices(const std::vector<std::string>& names);
+
+  void SetVolume(const std::string& level);
+
+  cricket::Session* GetFirstSession() { return sessions_[call_->id()][0]; }
+  void AddSession(cricket::Session* session) {
+    sessions_[call_->id()].push_back(session);
+  }
+
+  void PrintStats() const;
+  void SetupAcceptedCall();
+
+  typedef std::map<std::string, RosterItem> RosterMap;
+
+  Console *console_;
+  buzz::XmppClient* xmpp_client_;
+  rtc::Thread* worker_thread_;
+  rtc::NetworkManager* network_manager_;
+  cricket::PortAllocator* port_allocator_;
+  cricket::SessionManager* session_manager_;
+  cricket::SessionManagerTask* session_manager_task_;
+  cricket::MediaEngineInterface* media_engine_;
+  cricket::DataEngineInterface* data_engine_;
+  cricket::MediaSessionClient* media_client_;
+  MucMap mucs_;
+
+  cricket::Call* call_;
+  typedef std::map<uint32, std::vector<cricket::Session *> > SessionMap;
+  SessionMap sessions_;
+
+  buzz::HangoutPubSubClient* hangout_pubsub_client_;
+  bool incoming_call_;
+  bool auto_accept_;
+  std::string pmuc_domain_;
+  bool render_;
+  cricket::DataChannelType data_channel_type_;
+  bool multisession_enabled_;
+  cricket::VideoRenderer* local_renderer_;
+  StaticRenderedViews static_rendered_views_;
+  uint32 static_views_accumulated_count_;
+  uint32 screencast_ssrc_;
+
+  buzz::PresenceStatus my_status_;
+  buzz::PresencePushTask* presence_push_;
+  buzz::PresenceOutTask* presence_out_;
+  buzz::MucInviteRecvTask* muc_invite_recv_;
+  buzz::MucInviteSendTask* muc_invite_send_;
+  buzz::FriendInviteSendTask* friend_invite_send_;
+  RosterMap* roster_;
+  uint32 portallocator_flags_;
+
+  bool allow_local_ips_;
+  cricket::SignalingProtocol signaling_protocol_;
+  cricket::TransportProtocol transport_protocol_;
+  cricket::SecurePolicy sdes_policy_;
+  cricket::SecurePolicy dtls_policy_;
+  rtc::scoped_ptr<rtc::SSLIdentity> ssl_identity_;
+  std::string last_sent_to_;
+
+  bool show_roster_messages_;
+};
+
+#endif  // TALK_EXAMPLES_CALL_CALLCLIENT_H_
diff --git a/libjingle/examples/call/callclient_unittest.cc b/libjingle/examples/call/callclient_unittest.cc
new file mode 100644
index 0000000..256853a
--- /dev/null
+++ b/libjingle/examples/call/callclient_unittest.cc
@@ -0,0 +1,47 @@
+/*
+ * libjingle
+ * Copyright 2008, 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.
+ */
+
+// Unit tests for CallClient
+
+#include "talk/examples/call/callclient.h"
+#include "talk/media/base/filemediaengine.h"
+#include "talk/media/base/mediaengine.h"
+#include "webrtc/libjingle/xmpp/xmppthread.h"
+#include "webrtc/base/gunit.h"
+
+TEST(CallClientTest, CreateCallClientWithDefaultMediaEngine) {
+  buzz::XmppPump pump;
+  CallClient *client = new CallClient(pump.client(), "app", "version");
+  delete client;
+}
+
+TEST(CallClientTest, CreateCallClientWithFileMediaEngine) {
+  buzz::XmppPump pump;
+  CallClient *client = new CallClient(pump.client(), "app", "version");
+  client->SetMediaEngine(new cricket::FileMediaEngine);
+  delete client;
+}
diff --git a/libjingle/examples/call/console.cc b/libjingle/examples/call/console.cc
new file mode 100644
index 0000000..a0da2af
--- /dev/null
+++ b/libjingle/examples/call/console.cc
@@ -0,0 +1,169 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, 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.
+ */
+
+#define _CRT_SECURE_NO_DEPRECATE 1
+
+#include <assert.h>
+
+#ifdef POSIX
+#include <signal.h>
+#include <termios.h>
+#include <unistd.h>
+#endif  // POSIX
+
+#include "talk/examples/call/callclient.h"
+#include "talk/examples/call/console.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/messagequeue.h"
+#include "webrtc/base/stringutils.h"
+
+#ifdef POSIX
+static void DoNothing(int unused) {}
+#endif
+
+Console::Console(rtc::Thread *thread, CallClient *client) :
+  client_(client),
+  client_thread_(thread),
+  stopped_(false) {}
+
+Console::~Console() {
+  Stop();
+}
+
+void Console::Start() {
+  if (stopped_) {
+    // stdin was closed in Stop(), so we can't restart.
+    LOG(LS_ERROR) << "Cannot re-start";
+    return;
+  }
+  if (console_thread_) {
+    LOG(LS_WARNING) << "Already started";
+    return;
+  }
+  console_thread_.reset(new rtc::Thread());
+  console_thread_->Start();
+  console_thread_->Post(this, MSG_START);
+}
+
+void Console::Stop() {
+  if (console_thread_) {
+#ifdef WIN32
+    CloseHandle(GetStdHandle(STD_INPUT_HANDLE));
+#else
+    close(fileno(stdin));
+    // This forces the read() in fgets() to return with errno = EINTR. fgets()
+    // will retry the read() and fail, thus returning.
+    pthread_kill(console_thread_->GetPThread(), SIGUSR1);
+#endif
+    console_thread_->Stop();
+    console_thread_.reset();
+    stopped_ = true;
+  }
+}
+
+void Console::SetEcho(bool on) {
+#ifdef WIN32
+  HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
+  if ((hIn == INVALID_HANDLE_VALUE) || (hIn == NULL))
+    return;
+
+  DWORD mode;
+  if (!GetConsoleMode(hIn, &mode))
+    return;
+
+  if (on) {
+    mode = mode | ENABLE_ECHO_INPUT;
+  } else {
+    mode = mode & ~ENABLE_ECHO_INPUT;
+  }
+
+  SetConsoleMode(hIn, mode);
+#else
+  const int fd = fileno(stdin);
+  if (fd == -1)
+   return;
+
+  struct termios tcflags;
+  if (tcgetattr(fd, &tcflags) == -1)
+    return;
+
+  if (on) {
+    tcflags.c_lflag |= ECHO;
+  } else {
+    tcflags.c_lflag &= ~ECHO;
+  }
+
+  tcsetattr(fd, TCSANOW, &tcflags);
+#endif
+}
+
+void Console::PrintLine(const char* format, ...) {
+  va_list ap;
+  va_start(ap, format);
+
+  char buf[4096];
+  int size = vsnprintf(buf, sizeof(buf), format, ap);
+  assert(size >= 0);
+  assert(size < static_cast<int>(sizeof(buf)));
+  buf[size] = '\0';
+  printf("%s\n", buf);
+  fflush(stdout);
+
+  va_end(ap);
+}
+
+void Console::RunConsole() {
+  char input_buffer[128];
+  while (fgets(input_buffer, sizeof(input_buffer), stdin) != NULL) {
+    client_thread_->Post(this, MSG_INPUT,
+        new rtc::TypedMessageData<std::string>(input_buffer));
+  }
+}
+
+void Console::OnMessage(rtc::Message *msg) {
+  switch (msg->message_id) {
+    case MSG_START:
+#ifdef POSIX
+      // Install a no-op signal so that we can abort RunConsole() by raising
+      // SIGUSR1.
+      struct sigaction act;
+      act.sa_handler = &DoNothing;
+      sigemptyset(&act.sa_mask);
+      act.sa_flags = 0;
+      if (sigaction(SIGUSR1, &act, NULL) < 0) {
+        LOG(LS_WARNING) << "Can't install signal";
+      }
+#endif
+      RunConsole();
+      break;
+    case MSG_INPUT:
+      rtc::TypedMessageData<std::string> *data =
+          static_cast<rtc::TypedMessageData<std::string>*>(msg->pdata);
+      client_->ParseLine(data->data());
+      break;
+  }
+}
diff --git a/libjingle/examples/call/console.h b/libjingle/examples/call/console.h
new file mode 100644
index 0000000..abcae99
--- /dev/null
+++ b/libjingle/examples/call/console.h
@@ -0,0 +1,70 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, 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.
+ */
+
+#ifndef TALK_EXAMPLES_CALL_CONSOLE_H_
+#define TALK_EXAMPLES_CALL_CONSOLE_H_
+
+#include <stdio.h>
+
+#include "webrtc/base/messagequeue.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/base/thread.h"
+
+class CallClient;
+
+class Console : public rtc::MessageHandler {
+ public:
+  Console(rtc::Thread *thread, CallClient *client);
+  ~Console();
+
+  // Starts reading lines from the console and giving them to the CallClient.
+  void Start();
+  // Stops reading lines. Cannot be restarted.
+  void Stop();
+
+  virtual void OnMessage(rtc::Message *msg);
+
+  void PrintLine(const char* format, ...);
+
+  static void SetEcho(bool on);
+
+ private:
+  enum {
+    MSG_START,
+    MSG_INPUT,
+  };
+
+  void RunConsole();
+  void ParseLine(std::string &str);
+
+  CallClient *client_;
+  rtc::Thread *client_thread_;
+  rtc::scoped_ptr<rtc::Thread> console_thread_;
+  bool stopped_;
+};
+
+#endif // TALK_EXAMPLES_CALL_CONSOLE_H_
diff --git a/libjingle/examples/call/friendinvitesendtask.cc b/libjingle/examples/call/friendinvitesendtask.cc
new file mode 100644
index 0000000..e2b8dde
--- /dev/null
+++ b/libjingle/examples/call/friendinvitesendtask.cc
@@ -0,0 +1,76 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, 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 "talk/examples/call/friendinvitesendtask.h"
+#include "webrtc/libjingle/xmpp/constants.h"
+
+namespace buzz {
+
+XmppReturnStatus
+FriendInviteSendTask::Send(const Jid& user) {
+  if (GetState() != STATE_INIT && GetState() != STATE_START)
+    return XMPP_RETURN_BADSTATE;
+
+  // Need to first add to roster, then subscribe to presence.
+  XmlElement* iq = new XmlElement(QN_IQ);
+  iq->AddAttr(QN_TYPE, STR_SET);
+  XmlElement* query = new XmlElement(QN_ROSTER_QUERY);
+  XmlElement* item = new XmlElement(QN_ROSTER_ITEM);
+  item->AddAttr(QN_JID, user.Str());
+  item->AddAttr(QN_NAME, user.node());
+  query->AddElement(item);
+  iq->AddElement(query);
+  QueueStanza(iq);
+
+  // Subscribe to presence
+  XmlElement* presence = new XmlElement(QN_PRESENCE);
+  presence->AddAttr(QN_TO, user.Str());
+  presence->AddAttr(QN_TYPE, STR_SUBSCRIBE);
+  XmlElement* invitation = new XmlElement(QN_INVITATION);
+  invitation->AddAttr(QN_INVITE_MESSAGE,
+      "I've been using Google Talk and thought you might like to try it out. "
+      "We can use it to call each other for free over the internet. Here's an "
+      "invitation to download Google Talk. Give it a try!");
+  presence->AddElement(invitation);
+  QueueStanza(presence);
+
+  return XMPP_RETURN_OK;
+}
+
+int
+FriendInviteSendTask::ProcessStart() {
+  const XmlElement* stanza = NextStanza();
+  if (stanza == NULL)
+    return STATE_BLOCKED;
+
+  if (SendStanza(stanza) != XMPP_RETURN_OK)
+    return STATE_ERROR;
+
+  return STATE_START;
+}
+
+}
diff --git a/libjingle/examples/call/friendinvitesendtask.h b/libjingle/examples/call/friendinvitesendtask.h
new file mode 100644
index 0000000..2928452
--- /dev/null
+++ b/libjingle/examples/call/friendinvitesendtask.h
@@ -0,0 +1,49 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, 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.
+ */
+
+#ifndef _FRIENDINVITESENDTASK_H_
+#define _FRIENDINVITESENDTASK_H_
+
+#include "webrtc/libjingle/xmpp/xmppengine.h"
+#include "webrtc/libjingle/xmpp/xmpptask.h"
+
+namespace buzz {
+
+class FriendInviteSendTask : public XmppTask {
+public:
+  explicit FriendInviteSendTask(XmppTaskParentInterface* parent)
+    : XmppTask(parent) {}
+  virtual ~FriendInviteSendTask() {}
+
+  XmppReturnStatus Send(const Jid& user);
+
+  virtual int ProcessStart();
+};
+
+}
+
+#endif
diff --git a/libjingle/examples/call/mediaenginefactory.cc b/libjingle/examples/call/mediaenginefactory.cc
new file mode 100644
index 0000000..dd62bf8
--- /dev/null
+++ b/libjingle/examples/call/mediaenginefactory.cc
@@ -0,0 +1,81 @@
+//
+// libjingle
+// Copyright 2004--2007, 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 "talk/examples/call/mediaenginefactory.h"
+
+#include "talk/media/base/fakemediaengine.h"
+#include "talk/media/base/filemediaengine.h"
+#include "talk/media/base/mediaengine.h"
+#include "webrtc/base/stringutils.h"
+
+std::vector<cricket::AudioCodec> RequiredAudioCodecs() {
+  std::vector<cricket::AudioCodec> audio_codecs;
+  audio_codecs.push_back(
+      cricket::AudioCodec(9, "G722", 16000, 0, 1, 0));
+  audio_codecs.push_back(
+      cricket::AudioCodec(0, "PCMU", 8000, 0, 1, 0));
+  audio_codecs.push_back(
+      cricket::AudioCodec(13, "CN", 8000, 0, 1, 0));
+  audio_codecs.push_back(
+      cricket::AudioCodec(105, "CN", 16000, 0, 1, 0));
+  return audio_codecs;
+}
+
+std::vector<cricket::VideoCodec> RequiredVideoCodecs() {
+  std::vector<cricket::VideoCodec> video_codecs;
+  video_codecs.push_back(
+      cricket::VideoCodec(97, "H264", 320, 240, 30, 0));
+  video_codecs.push_back(
+      cricket::VideoCodec(99, "H264-SVC", 640, 360, 30, 0));
+  return video_codecs;
+}
+
+cricket::MediaEngineInterface* MediaEngineFactory::CreateFileMediaEngine(
+    const char* voice_in, const char* voice_out,
+    const char* video_in, const char* video_out) {
+  cricket::FileMediaEngine* file_media_engine = new cricket::FileMediaEngine;
+  // Set the RTP dump file names.
+  if (voice_in) {
+    file_media_engine->set_voice_input_filename(voice_in);
+  }
+  if (voice_out) {
+    file_media_engine->set_voice_output_filename(voice_out);
+  }
+  if (video_in) {
+    file_media_engine->set_video_input_filename(video_in);
+  }
+  if (video_out) {
+    file_media_engine->set_video_output_filename(video_out);
+  }
+
+  // Set voice and video codecs. TODO: The codecs actually depend on
+  // the the input voice and video streams.
+  file_media_engine->set_voice_codecs(RequiredAudioCodecs());
+  file_media_engine->set_video_codecs(RequiredVideoCodecs());
+
+  return file_media_engine;
+}
diff --git a/libjingle/examples/call/mediaenginefactory.h b/libjingle/examples/call/mediaenginefactory.h
new file mode 100644
index 0000000..90407f9
--- /dev/null
+++ b/libjingle/examples/call/mediaenginefactory.h
@@ -0,0 +1,40 @@
+/*
+ * libjingle
+ * Copyright 2011, 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.
+ */
+
+#ifndef TALK_EXAMPLES_CALL_MEDIAENGINEFACTORY_H_
+#define TALK_EXAMPLES_CALL_MEDIAENGINEFACTORY_H_
+
+#include "talk/media/base/mediaengine.h"
+
+class MediaEngineFactory {
+ public:
+  static cricket::MediaEngineInterface* CreateFileMediaEngine(
+      const char* voice_in, const char* voice_out,
+      const char* video_in, const char* video_out);
+};
+
+#endif  // TALK_EXAMPLES_CALL_MEDIAENGINEFACTORY_H_
diff --git a/libjingle/examples/call/muc.h b/libjingle/examples/call/muc.h
new file mode 100644
index 0000000..9be192f
--- /dev/null
+++ b/libjingle/examples/call/muc.h
@@ -0,0 +1,66 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, 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.
+ */
+
+#ifndef _MUC_H_
+#define _MUC_H_
+
+#include <map>
+#include "webrtc/libjingle/xmpp/jid.h"
+#include "webrtc/libjingle/xmpp/presencestatus.h"
+
+namespace buzz {
+
+class Muc {
+ public:
+   Muc(const Jid& jid, const std::string& nick) : state_(MUC_JOINING),
+       jid_(jid), local_jid_(Jid(jid.Str() + "/" + nick)) {}
+  ~Muc() {};
+
+  enum State { MUC_JOINING, MUC_JOINED, MUC_LEAVING };
+  State state() const { return state_; }
+  void set_state(State state) { state_ = state; }
+  const Jid & jid() const { return jid_; }
+  const Jid & local_jid() const { return local_jid_; }
+
+  typedef std::map<std::string, MucPresenceStatus> MemberMap;
+
+  // All the intelligence about how to manage the members is in
+  // CallClient, so we completely expose the map.
+  MemberMap& members() {
+    return members_;
+  }
+
+private:
+  State state_;
+  Jid jid_;
+  Jid local_jid_;
+  MemberMap members_;
+};
+
+}
+
+#endif
diff --git a/libjingle/examples/call/mucinviterecvtask.cc b/libjingle/examples/call/mucinviterecvtask.cc
new file mode 100644
index 0000000..32de8fe
--- /dev/null
+++ b/libjingle/examples/call/mucinviterecvtask.cc
@@ -0,0 +1,124 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, 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 "talk/examples/call/mucinviterecvtask.h"
+#include "webrtc/libjingle/xmpp/constants.h"
+
+namespace buzz {
+
+const char* types[] = {
+  "unknown",
+  "audio",
+  "video",
+};
+
+const char* statuses[] = {
+  "unknown",
+  "sendrecv",
+  "sendonly",
+  "recvonly",
+  "inactive",
+};
+
+const char*
+AvailableMediaEntry::TypeAsString(type_t type) {
+  // The values of the constants have been chosen such that this is correct.
+  return types[type];
+}
+
+const char*
+AvailableMediaEntry::StatusAsString(status_t status) {
+  // The values of the constants have been chosen such that this is correct.
+  return statuses[status];
+}
+
+int bodytext_to_array_pos(const XmlElement* elem, const char* array[],
+    int len, int defval = -1) {
+  if (elem) {
+    const std::string& body(elem->BodyText());
+    for (int i = 0; i < len; ++i) {
+      if (body == array[i]) {
+        // Found it.
+        return i;
+      }
+    }
+  }
+  // If we get here, it's not any value in the array.
+  return defval;
+}
+
+bool
+MucInviteRecvTask::HandleStanza(const XmlElement* stanza) {
+  // Figuring out that we want to handle this is a lot of the work of
+  // actually handling it, so we handle it right here instead of queueing it.
+  const XmlElement* xstanza;
+  const XmlElement* invite;
+  if (stanza->Name() != QN_MESSAGE) return false;
+  xstanza = stanza->FirstNamed(QN_MUC_USER_X);
+  if (!xstanza) return false;
+  invite = xstanza->FirstNamed(QN_MUC_USER_INVITE);
+  if (!invite) return false;
+  // Else it's an invite and we definitely want to handle it. Parse the
+  // available-media, if any.
+  std::vector<AvailableMediaEntry> v;
+  const XmlElement* avail =
+    invite->FirstNamed(QN_GOOGLE_MUC_USER_AVAILABLE_MEDIA);
+  if (avail) {
+    for (const XmlElement* entry = avail->FirstNamed(QN_GOOGLE_MUC_USER_ENTRY);
+        entry;
+        entry = entry->NextNamed(QN_GOOGLE_MUC_USER_ENTRY)) {
+      AvailableMediaEntry tmp;
+      // In the interest of debugging, we accept as much valid-looking data
+      // as we can.
+      tmp.label = atoi(entry->Attr(QN_LABEL).c_str());
+      tmp.type = static_cast<AvailableMediaEntry::type_t>(
+          bodytext_to_array_pos(
+              entry->FirstNamed(QN_GOOGLE_MUC_USER_TYPE),
+              types,
+              sizeof(types)/sizeof(const char*),
+              AvailableMediaEntry::TYPE_UNKNOWN));
+      tmp.status = static_cast<AvailableMediaEntry::status_t>(
+          bodytext_to_array_pos(
+              entry->FirstNamed(QN_GOOGLE_MUC_USER_STATUS),
+              statuses,
+              sizeof(statuses)/sizeof(const char*),
+              AvailableMediaEntry::STATUS_UNKNOWN));
+      v.push_back(tmp);
+    }
+  }
+  SignalInviteReceived(Jid(invite->Attr(QN_FROM)), Jid(stanza->Attr(QN_FROM)),
+      v);
+  return true;
+}
+
+int
+MucInviteRecvTask::ProcessStart() {
+  // We never queue anything so we are always blocked.
+  return STATE_BLOCKED;
+}
+
+}
diff --git a/libjingle/examples/call/mucinviterecvtask.h b/libjingle/examples/call/mucinviterecvtask.h
new file mode 100644
index 0000000..0466c94
--- /dev/null
+++ b/libjingle/examples/call/mucinviterecvtask.h
@@ -0,0 +1,82 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, 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.
+ */
+
+#ifndef _MUCINVITERECVTASK_H_
+#define _MUCINVITERECVTASK_H_
+
+#include <vector>
+
+#include "webrtc/libjingle/xmpp/xmppengine.h"
+#include "webrtc/libjingle/xmpp/xmpptask.h"
+#include "webrtc/base/sigslot.h"
+
+namespace buzz {
+
+struct AvailableMediaEntry {
+  enum type_t {
+    // SIP defines other media types, but these are the only ones we use in
+    // multiway jingle.
+    // These numbers are important; see .cc file
+    TYPE_UNKNOWN = 0, // indicates invalid string
+    TYPE_AUDIO = 1,
+    TYPE_VIDEO = 2,
+  };
+
+  enum status_t {
+    // These numbers are important; see .cc file
+    STATUS_UNKNOWN = 0, // indicates invalid string
+    STATUS_SENDRECV = 1,
+    STATUS_SENDONLY = 2,
+    STATUS_RECVONLY = 3,
+    STATUS_INACTIVE = 4,
+  };
+
+  uint32 label;
+  type_t type;
+  status_t status;
+
+  static const char* TypeAsString(type_t type);
+  static const char* StatusAsString(status_t status);
+};
+
+class MucInviteRecvTask : public XmppTask {
+ public:
+  explicit MucInviteRecvTask(XmppTaskParentInterface* parent)
+      : XmppTask(parent, XmppEngine::HL_TYPE) {}
+  virtual int ProcessStart();
+
+  // First arg is inviter's JID; second is MUC's JID.
+  sigslot::signal3<const Jid&, const Jid&, const std::vector<AvailableMediaEntry>& > SignalInviteReceived;
+
+ protected:
+  virtual bool HandleStanza(const XmlElement* stanza);
+
+};
+
+}
+
+#endif
diff --git a/libjingle/examples/call/mucinvitesendtask.cc b/libjingle/examples/call/mucinvitesendtask.cc
new file mode 100644
index 0000000..2299b78
--- /dev/null
+++ b/libjingle/examples/call/mucinvitesendtask.cc
@@ -0,0 +1,63 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, 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 "talk/examples/call/mucinvitesendtask.h"
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/libjingle/xmpp/xmppclient.h"
+
+namespace buzz {
+
+XmppReturnStatus
+MucInviteSendTask::Send(const Jid& to, const Jid& invitee) {
+  if (GetState() != STATE_INIT && GetState() != STATE_START)
+    return XMPP_RETURN_BADSTATE;
+
+  XmlElement* message = new XmlElement(QN_MESSAGE);
+  message->AddAttr(QN_TO, to.Str());
+  XmlElement* xstanza = new XmlElement(QN_MUC_USER_X);
+  XmlElement* invite = new XmlElement(QN_MUC_USER_INVITE);
+  invite->AddAttr(QN_TO, invitee.Str());
+  xstanza->AddElement(invite);
+  message->AddElement(xstanza);
+
+  QueueStanza(message);
+  return XMPP_RETURN_OK;
+}
+
+int
+MucInviteSendTask::ProcessStart() {
+  const XmlElement* stanza = NextStanza();
+  if (stanza == NULL)
+    return STATE_BLOCKED;
+
+  if (SendStanza(stanza) != XMPP_RETURN_OK)
+    return STATE_ERROR;
+
+  return STATE_START;
+}
+
+}
diff --git a/libjingle/examples/call/mucinvitesendtask.h b/libjingle/examples/call/mucinvitesendtask.h
new file mode 100644
index 0000000..673efb0
--- /dev/null
+++ b/libjingle/examples/call/mucinvitesendtask.h
@@ -0,0 +1,50 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, 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.
+ */
+
+#ifndef _MUCINVITESENDTASK_H_
+#define _MUCINVITESENDTASK_H_
+
+#include "talk/examples/call/muc.h"
+#include "webrtc/libjingle/xmpp/xmppengine.h"
+#include "webrtc/libjingle/xmpp/xmpptask.h"
+
+namespace buzz {
+
+class MucInviteSendTask : public XmppTask {
+public:
+  explicit MucInviteSendTask(XmppTaskParentInterface* parent)
+      : XmppTask(parent) {}
+  virtual ~MucInviteSendTask() {}
+
+  XmppReturnStatus Send(const Jid& to, const Jid& invitee);
+
+  virtual int ProcessStart();
+};
+
+}
+
+#endif
diff --git a/libjingle/examples/call/presencepushtask.cc b/libjingle/examples/call/presencepushtask.cc
new file mode 100644
index 0000000..8d8dca8
--- /dev/null
+++ b/libjingle/examples/call/presencepushtask.cc
@@ -0,0 +1,222 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, 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 "talk/examples/call/presencepushtask.h"
+
+#include "talk/examples/call/muc.h"
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/base/stringencode.h"
+
+
+
+namespace buzz {
+
+// string helper functions -----------------------------------------------------
+
+static bool
+IsXmlSpace(int ch) {
+  return ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t';
+}
+
+static bool ListContainsToken(const std::string & list,
+                              const std::string & token) {
+  size_t i = list.find(token);
+  if (i == std::string::npos || token.empty())
+    return false;
+  bool boundary_before = (i == 0 || IsXmlSpace(list[i - 1]));
+  bool boundary_after = (i == list.length() - token.length() ||
+                         IsXmlSpace(list[i + token.length()]));
+  return boundary_before && boundary_after;
+}
+
+
+bool PresencePushTask::HandleStanza(const XmlElement * stanza) {
+  if (stanza->Name() != QN_PRESENCE)
+    return false;
+  QueueStanza(stanza);
+  return true;
+}
+
+static bool IsUtf8FirstByte(int c) {
+  return (((c)&0x80)==0) || // is single byte
+    ((unsigned char)((c)-0xc0)<0x3e); // or is lead byte
+}
+
+int PresencePushTask::ProcessStart() {
+  const XmlElement * stanza = NextStanza();
+  if (stanza == NULL)
+    return STATE_BLOCKED;
+
+  Jid from(stanza->Attr(QN_FROM));
+  std::map<Jid, buzz::Muc*>::const_iterator elem =
+      client_->mucs().find(from.BareJid());
+  if (elem == client_->mucs().end()) {
+    HandlePresence(from, stanza);
+  } else {
+    HandleMucPresence(elem->second, from, stanza);
+  }
+
+  return STATE_START;
+}
+
+void PresencePushTask::HandlePresence(const Jid& from,
+                                      const XmlElement* stanza) {
+  if (stanza->Attr(QN_TYPE) == STR_ERROR)
+    return;
+
+  PresenceStatus s;
+  FillStatus(from, stanza, &s);
+  SignalStatusUpdate(s);
+}
+
+void PresencePushTask::HandleMucPresence(buzz::Muc* muc,
+                                         const Jid& from,
+                                         const XmlElement* stanza) {
+  if (from == muc->local_jid()) {
+    if (!stanza->HasAttr(QN_TYPE)) {
+      // We joined the MUC.
+      const XmlElement* elem = stanza->FirstNamed(QN_MUC_USER_X);
+      // Status code=110 or 100 is not guaranteed to be present, so we
+      // only check the item element and Muc join status.
+      if (elem) {
+        if (elem->FirstNamed(QN_MUC_USER_ITEM) &&
+            muc->state() == buzz::Muc::MUC_JOINING) {
+          SignalMucJoined(muc->jid());
+        }
+      }
+    } else {
+      // We've been kicked. Bye.
+      int error = 0;
+      if (stanza->Attr(QN_TYPE) == STR_ERROR) {
+        const XmlElement* elem = stanza->FirstNamed(QN_ERROR);
+        if (elem && elem->HasAttr(QN_CODE)) {
+          error = atoi(elem->Attr(QN_CODE).c_str());
+        }
+      }
+      SignalMucLeft(muc->jid(), error);
+    }
+  } else {
+    MucPresenceStatus s;
+    FillMucStatus(from, stanza, &s);
+    SignalMucStatusUpdate(muc->jid(), s);
+  }
+}
+
+void PresencePushTask::FillStatus(const Jid& from, const XmlElement* stanza,
+                                  PresenceStatus* s) {
+  s->set_jid(from);
+  if (stanza->Attr(QN_TYPE) == STR_UNAVAILABLE) {
+    s->set_available(false);
+  } else {
+    s->set_available(true);
+    const XmlElement * status = stanza->FirstNamed(QN_STATUS);
+    if (status != NULL) {
+      s->set_status(status->BodyText());
+
+      // Truncate status messages longer than 300 bytes
+      if (s->status().length() > 300) {
+        size_t len = 300;
+
+        // Be careful not to split legal utf-8 chars in half
+        while (!IsUtf8FirstByte(s->status()[len]) && len > 0) {
+          len -= 1;
+        }
+        std::string truncated(s->status(), 0, len);
+        s->set_status(truncated);
+      }
+    }
+
+    const XmlElement * priority = stanza->FirstNamed(QN_PRIORITY);
+    if (priority != NULL) {
+      int pri;
+      if (rtc::FromString(priority->BodyText(), &pri)) {
+        s->set_priority(pri);
+      }
+    }
+
+    const XmlElement * show = stanza->FirstNamed(QN_SHOW);
+    if (show == NULL || show->FirstChild() == NULL) {
+      s->set_show(PresenceStatus::SHOW_ONLINE);
+    }
+    else {
+      if (show->BodyText() == "away") {
+        s->set_show(PresenceStatus::SHOW_AWAY);
+      }
+      else if (show->BodyText() == "xa") {
+        s->set_show(PresenceStatus::SHOW_XA);
+      }
+      else if (show->BodyText() == "dnd") {
+        s->set_show(PresenceStatus::SHOW_DND);
+      }
+      else if (show->BodyText() == "chat") {
+        s->set_show(PresenceStatus::SHOW_CHAT);
+      }
+      else {
+        s->set_show(PresenceStatus::SHOW_ONLINE);
+      }
+    }
+
+    const XmlElement * caps = stanza->FirstNamed(QN_CAPS_C);
+    if (caps != NULL) {
+      std::string node = caps->Attr(QN_NODE);
+      std::string ver = caps->Attr(QN_VER);
+      std::string exts = caps->Attr(QN_EXT);
+
+      s->set_know_capabilities(true);
+      s->set_caps_node(node);
+      s->set_version(ver);
+
+      if (ListContainsToken(exts, "voice-v1")) {
+        s->set_voice_capability(true);
+      }
+      if (ListContainsToken(exts, "video-v1")) {
+        s->set_video_capability(true);
+      }
+    }
+
+    const XmlElement* delay = stanza->FirstNamed(kQnDelayX);
+    if (delay != NULL) {
+      // Ideally we would parse this according to the Psuedo ISO-8601 rules
+      // that are laid out in JEP-0082:
+      // http://www.jabber.org/jeps/jep-0082.html
+      std::string stamp = delay->Attr(kQnStamp);
+      s->set_sent_time(stamp);
+    }
+
+    const XmlElement* nick = stanza->FirstNamed(QN_NICKNAME);
+    if (nick) {
+      s->set_nick(nick->BodyText());
+    }
+  }
+}
+
+void PresencePushTask::FillMucStatus(const Jid& from, const XmlElement* stanza,
+                                     MucPresenceStatus* s) {
+  FillStatus(from, stanza, s);
+}
+
+}
diff --git a/libjingle/examples/call/presencepushtask.h b/libjingle/examples/call/presencepushtask.h
new file mode 100644
index 0000000..e3d3268
--- /dev/null
+++ b/libjingle/examples/call/presencepushtask.h
@@ -0,0 +1,70 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, 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.
+ */
+
+#ifndef _PRESENCEPUSHTASK_H_
+#define _PRESENCEPUSHTASK_H_
+
+#include <vector>
+
+#include "talk/examples/call/callclient.h"
+#include "webrtc/libjingle/xmpp/presencestatus.h"
+#include "webrtc/libjingle/xmpp/xmppengine.h"
+#include "webrtc/libjingle/xmpp/xmpptask.h"
+#include "webrtc/base/sigslot.h"
+
+namespace buzz {
+
+class PresencePushTask : public XmppTask {
+ public:
+  PresencePushTask(XmppTaskParentInterface* parent, CallClient* client)
+    : XmppTask(parent, XmppEngine::HL_TYPE),
+      client_(client) {}
+  virtual int ProcessStart();
+
+  sigslot::signal1<const PresenceStatus&> SignalStatusUpdate;
+  sigslot::signal1<const Jid&> SignalMucJoined;
+  sigslot::signal2<const Jid&, int> SignalMucLeft;
+  sigslot::signal2<const Jid&, const MucPresenceStatus&> SignalMucStatusUpdate;
+
+ protected:
+  virtual bool HandleStanza(const XmlElement * stanza);
+  void HandlePresence(const Jid& from, const XmlElement * stanza);
+  void HandleMucPresence(buzz::Muc* muc,
+                         const Jid& from, const XmlElement * stanza);
+  static void FillStatus(const Jid& from, const XmlElement * stanza,
+                         PresenceStatus* status);
+  static void FillMucStatus(const Jid& from, const XmlElement * stanza,
+                            MucPresenceStatus* status);
+
+ private:
+  CallClient* client_;
+};
+
+
+}
+
+#endif
diff --git a/libjingle/examples/login/login_main.cc b/libjingle/examples/login/login_main.cc
new file mode 100644
index 0000000..581c986
--- /dev/null
+++ b/libjingle/examples/login/login_main.cc
@@ -0,0 +1,67 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, 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 <stdio.h>
+
+#include <iostream>
+
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/libjingle/xmpp/xmppclientsettings.h"
+#include "webrtc/libjingle/xmpp/xmppengine.h"
+#include "webrtc/libjingle/xmpp/xmppthread.h"
+#include "webrtc/base/thread.h"
+
+int main(int argc, char **argv) {
+  std::cout << "OAuth Access Token: ";
+  std::string auth_token;
+  std::getline(std::cin, auth_token);
+
+  std::cout << "User Name: ";
+  std::string username;
+  std::getline(std::cin, username);
+
+  // Start xmpp on a different thread
+  buzz::XmppThread thread;
+  thread.Start();
+
+  buzz::XmppClientSettings xcs;
+  xcs.set_user(username.c_str());
+  xcs.set_host("gmail.com");
+  xcs.set_use_tls(buzz::TLS_DISABLED);
+  xcs.set_auth_token(buzz::AUTH_MECHANISM_OAUTH2,
+                     auth_token.c_str());
+  xcs.set_server(rtc::SocketAddress("talk.google.com", 5222));
+  thread.Login(xcs);
+
+  // Use main thread for console input
+  std::string line;
+  while (std::getline(std::cin, line)) {
+    if (line == "quit")
+      break;
+  }
+  return 0;
+}
diff --git a/libjingle/libjingle.gyp b/libjingle/libjingle.gyp
new file mode 100644
index 0000000..ed8e226
--- /dev/null
+++ b/libjingle/libjingle.gyp
@@ -0,0 +1,174 @@
+# Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
+#
+# Use of this source code is governed by a BSD-style license
+# that can be found in the LICENSE file in the root of the source
+# tree. An additional intellectual property rights grant can be found
+# in the file PATENTS.  All contributing project authors may
+# be found in the AUTHORS file in the root of the source tree.
+
+{
+  'includes': ['../build/common.gypi'],
+  'variables': {
+    'talk_root%': '<(webrtc_root)/../talk',
+  },
+  'targets': [
+    {
+      'target_name': 'jingle_session',
+      'type': 'static_library',
+      'dependencies': [
+        '<(talk_root)/libjingle.gyp:libjingle_media',
+        '<(webrtc_root)/base/base.gyp:webrtc_base',
+        '<(webrtc_root)/libjingle/xmpp/xmpp.gyp:rtc_xmpp',
+        '<(DEPTH)/third_party/expat/expat.gyp:expat',
+      ],
+      'cflags_cc!': [
+        '-Wnon-virtual-dtor',
+      ],
+      'export_dependent_settings': [
+        '<(DEPTH)/third_party/expat/expat.gyp:expat',
+      ],
+      'sources': [
+        'media/call.cc',
+        'media/call.h',
+        'media/mediasessionclient.cc',
+        'media/mediasessionclient.h',
+        'media/mediamessages.cc',
+        'media/mediamessages.h',
+        'session/sessionclient.h',
+        'session/sessionmanagertask.h',
+        'session/sessionsendtask.h',
+        'tunnel/pseudotcp.cc',
+        'tunnel/pseudotcp.h',
+        'tunnel/pseudotcpchannel.cc',
+        'tunnel/pseudotcpchannel.h',
+        'tunnel/tunnelsessionclient.cc',
+        'tunnel/tunnelsessionclient.h',
+        'tunnel/securetunnelsessionclient.cc',
+        'tunnel/securetunnelsessionclient.h',
+      ],
+      'direct_dependent_settings': {
+        'cflags_cc!': [
+          '-Wnon-virtual-dtor',
+        ],
+        'defines': [
+          'FEATURE_ENABLE_VOICEMAIL',
+        ],
+      },
+      'conditions': [
+        ['build_with_chromium==0', {
+          'defines': [
+            'FEATURE_ENABLE_VOICEMAIL',
+            'FEATURE_ENABLE_PSTN',
+          ],
+        }],
+      ],
+    },
+    {
+      'target_name': 'jingle_unittest',
+      'type': 'executable',
+      'dependencies': [
+        'jingle_session',
+        '<(DEPTH)/third_party/libsrtp/libsrtp.gyp:libsrtp',
+        '<(webrtc_root)/base/base_tests.gyp:rtc_base_tests_utils',
+        '<(talk_root)/libjingle.gyp:libjingle',
+        '<(talk_root)/libjingle.gyp:libjingle_p2p',
+        '<(talk_root)/libjingle_tests.gyp:libjingle_unittest_main',
+      ],
+      'include_dirs': [
+        '<(DEPTH)/third_party/libsrtp/srtp',
+      ],
+      'sources': [
+        'media/mediamessages_unittest.cc',
+        'media/mediasessionclient_unittest.cc',
+        'tunnel/pseudotcp_unittest.cc',
+      ],
+      'conditions': [
+        ['OS=="win"', {
+          'msvs_settings': {
+            'VCLinkerTool': {
+              'AdditionalDependencies': [
+                'strmiids.lib',
+              ],
+            },
+          },
+        }],
+      ],
+    },  # target jingle_unittest
+    {
+      'target_name': 'libjingle_xmpphelp',
+      'type': 'static_library',
+      'dependencies': [
+        '<(DEPTH)/third_party/expat/expat.gyp:expat',
+        '<(talk_root)/libjingle.gyp:libjingle',
+        '<(talk_root)/libjingle.gyp:libjingle_p2p',
+      ],
+      'sources': [
+        'xmpp/jingleinfotask.cc',
+        'xmpp/jingleinfotask.h',
+      ],
+    },  # target libjingle_xmpphelp
+    {
+      'target_name': 'login',
+      'type': 'executable',
+      'dependencies': [
+        'libjingle_xmpphelp',
+        '<(talk_root)/libjingle.gyp:libjingle',
+      ],
+      'sources': [
+        'examples/login/login_main.cc',
+      ],
+    },  # target login
+  ],
+  'conditions': [
+    # TODO(ronghuawu): Reenable building call.
+    # ['OS!="android"', {
+    #   'targets': [
+    #     {
+    #       'target_name': 'call',
+    #       'type': 'executable',
+    #       'dependencies': [
+    #         'libjingle.gyp:libjingle_p2p',
+    #         'libjingle_xmpphelp',
+    #       ],
+    #       'sources': [
+    #         'examples/call/call_main.cc',
+    #         'examples/call/callclient.cc',
+    #         'examples/call/callclient.h',
+    #         'examples/call/console.cc',
+    #         'examples/call/console.h',
+    #         'examples/call/friendinvitesendtask.cc',
+    #         'examples/call/friendinvitesendtask.h',
+    #         'examples/call/mediaenginefactory.cc',
+    #         'examples/call/mediaenginefactory.h',
+    #         'examples/call/muc.h',
+    #         'examples/call/mucinviterecvtask.cc',
+    #         'examples/call/mucinviterecvtask.h',
+    #         'examples/call/mucinvitesendtask.cc',
+    #         'examples/call/mucinvitesendtask.h',
+    #         'examples/call/presencepushtask.cc',
+    #         'examples/call/presencepushtask.h',
+    #       ],
+    #       'conditions': [
+    #         ['OS=="linux"', {
+    #           'link_settings': {
+    #             'libraries': [
+    #               '<!@(pkg-config --libs-only-l gobject-2.0 gthread-2.0'
+    #                   ' gtk+-2.0)',
+    #             ],
+    #           },
+    #         }],
+    #         ['OS=="win"', {
+    #           'msvs_settings': {
+    #             'VCLinkerTool': {
+    #               'AdditionalDependencies': [
+    #                 'strmiids.lib',
+    #               ],
+    #             },
+    #           },
+    #         }],
+    #       ],  # conditions
+    #     },  # target call
+    #   ], # targets
+    # }],  # OS!="android"
+  ],
+}
diff --git a/libjingle/media/call.cc b/libjingle/media/call.cc
new file mode 100644
index 0000000..f42ec30
--- /dev/null
+++ b/libjingle/media/call.cc
@@ -0,0 +1,1132 @@
+/*
+ * 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 "talk/media/base/constants.h"
+#include "talk/media/base/screencastid.h"
+#include "webrtc/libjingle/session/parsing.h"
+#include "webrtc/libjingle/media/call.h"
+#include "talk/session/media/currentspeakermonitor.h"
+#include "webrtc/libjingle/media/mediasessionclient.h"
+#include "webrtc/base/helpers.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/thread.h"
+#include "webrtc/base/window.h"
+
+namespace cricket {
+
+const uint32 MSG_CHECKAUTODESTROY = 1;
+const uint32 MSG_TERMINATECALL = 2;
+const uint32 MSG_PLAYDTMF = 3;
+
+namespace {
+const int kDTMFDelay = 300;  // msec
+const size_t kMaxDTMFDigits = 30;
+const int kSendToVoicemailTimeout = 1000*20;
+const int kNoVoicemailTimeout = 1000*180;
+const int kMediaMonitorInterval = 1000*15;
+// In order to be the same as the server-side switching, this must be 100.
+const int kAudioMonitorPollPeriodMillis = 100;
+
+// V is a pointer type.
+template<class K, class V>
+V FindOrNull(const std::map<K, V>& map,
+             const K& key) {
+  typename std::map<K, V>::const_iterator it = map.find(key);
+  return (it != map.end()) ? it->second : NULL;
+}
+
+
+bool ContentContainsCrypto(const cricket::ContentInfo* content) {
+  if (content != NULL) {
+    const cricket::MediaContentDescription* desc =
+        static_cast<const cricket::MediaContentDescription*>(
+            content->description);
+    if (!desc || desc->cryptos().empty()) {
+      return false;
+    }
+  }
+  return true;
+}
+
+}
+
+AudioSourceProxy::AudioSourceProxy(Call* call)
+    : call_(call) {
+  call_->SignalAudioMonitor.connect(this, &AudioSourceProxy::OnAudioMonitor);
+  call_->SignalMediaStreamsUpdate.connect(
+      this, &AudioSourceProxy::OnMediaStreamsUpdate);
+}
+
+void AudioSourceProxy::OnAudioMonitor(Call* call, const AudioInfo& info) {
+  SignalAudioMonitor(this, info);
+}
+
+void AudioSourceProxy::OnMediaStreamsUpdate(Call* call, Session* session,
+    const MediaStreams& added, const MediaStreams& removed) {
+  SignalMediaStreamsUpdate(this, session, added, removed);
+}
+
+Call::Call(MediaSessionClient* session_client)
+    : id_(rtc::CreateRandomId()),
+      session_client_(session_client),
+      has_video_(false),
+      has_data_(false),
+      muted_(false),
+      video_muted_(false),
+      send_to_voicemail_(true),
+      playing_dtmf_(false) {
+  audio_source_proxy_.reset(new AudioSourceProxy(this));
+}
+
+Call::~Call() {
+  while (media_session_map_.begin() != media_session_map_.end()) {
+    Session* session = media_session_map_.begin()->second.session;
+    RemoveSession(session);
+    session_client_->session_manager()->DestroySession(session);
+  }
+  rtc::Thread::Current()->Clear(this);
+}
+
+Session* Call::InitiateSession(const buzz::Jid& to,
+                               const buzz::Jid& initiator,
+                               const CallOptions& options) {
+  std::string id;
+  std::string initiator_name = initiator.Str();
+  return InternalInitiateSession(id, to, initiator_name, options);
+}
+
+Session *Call::InitiateSession(const std::string& id,
+                               const buzz::Jid& to,
+                               const CallOptions& options) {
+  std::string initiator_name;
+  return InternalInitiateSession(id, to, initiator_name, options);
+}
+
+void Call::IncomingSession(Session* session, const SessionDescription* offer) {
+  AddSession(session, offer);
+
+  // Make sure the session knows about the incoming ssrcs. This needs to be done
+  // prior to the SignalSessionState call, because that may trigger handling of
+  // these new SSRCs, so they need to be registered before then.
+  UpdateRemoteMediaStreams(session, offer->contents(), false);
+
+  // Missed the first state, the initiate, which is needed by
+  // call_client.
+  SignalSessionState(this, session, Session::STATE_RECEIVEDINITIATE);
+}
+
+void Call::AcceptSession(Session* session,
+                         const cricket::CallOptions& options) {
+  MediaSessionMap::iterator it = media_session_map_.find(session->id());
+  if (it != media_session_map_.end()) {
+    const SessionDescription* answer = session_client_->CreateAnswer(
+        session->remote_description(), options);
+    it->second.session->Accept(answer);
+  }
+}
+
+void Call::RejectSession(Session* session) {
+  // Assume polite decline.
+  MediaSessionMap::iterator it = media_session_map_.find(session->id());
+  if (it != media_session_map_.end())
+    it->second.session->Reject(STR_TERMINATE_DECLINE);
+}
+
+void Call::TerminateSession(Session* session) {
+  MediaSessionMap::iterator it = media_session_map_.find(session->id());
+  if (it != media_session_map_.end()) {
+    // Assume polite terminations.
+    it->second.session->Terminate();
+  }
+}
+
+void Call::Terminate() {
+  // Copy the list so that we can iterate over it in a stable way
+  std::vector<Session*> sessions = this->sessions();
+
+  // There may be more than one session to terminate
+  std::vector<Session*>::iterator it;
+  for (it = sessions.begin(); it != sessions.end(); ++it) {
+    TerminateSession(*it);
+  }
+}
+
+bool Call::SendViewRequest(Session* session,
+                           const ViewRequest& view_request) {
+  StaticVideoViews::const_iterator it;
+  for (it = view_request.static_video_views.begin();
+       it != view_request.static_video_views.end(); ++it) {
+    StreamParams found_stream;
+    bool found = false;
+    MediaStreams* recv_streams = GetMediaStreams(session);
+    if (recv_streams)
+      found = recv_streams->GetVideoStream(it->selector, &found_stream);
+    if (!found) {
+      LOG(LS_WARNING) << "Trying to send view request for ("
+                      << it->selector.ssrc << ", '"
+                      << it->selector.groupid << "', '"
+                      << it->selector.streamid << "'"
+                      << ") is not in the local streams.";
+      return false;
+    }
+  }
+
+  XmlElements elems;
+  WriteError error;
+  if (!WriteJingleViewRequest(CN_VIDEO, view_request, &elems, &error)) {
+    LOG(LS_ERROR) << "Couldn't write out view request: " << error.text;
+    return false;
+  }
+
+  return session->SendInfoMessage(elems, session->remote_name());
+}
+
+void Call::SetVideoRenderer(Session* session, uint32 ssrc,
+                            VideoRenderer* renderer) {
+  VideoChannel* video_channel = GetVideoChannel(session);
+  if (video_channel) {
+    video_channel->SetRenderer(ssrc, renderer);
+    LOG(LS_INFO) << "Set renderer of ssrc " << ssrc
+                 << " to " << renderer << ".";
+  } else {
+    LOG(LS_INFO) << "Failed to set renderer of ssrc " << ssrc << ".";
+  }
+}
+
+void Call::OnMessage(rtc::Message* message) {
+  switch (message->message_id) {
+  case MSG_CHECKAUTODESTROY:
+    // If no more sessions for this call, delete it
+    if (media_session_map_.empty())
+      session_client_->DestroyCall(this);
+    break;
+  case MSG_TERMINATECALL:
+    // Signal to the user that a timeout has happened and the call should
+    // be sent to voicemail.
+    if (send_to_voicemail_) {
+      SignalSetupToCallVoicemail();
+    }
+
+    // Callee didn't answer - terminate call
+    Terminate();
+    break;
+  case MSG_PLAYDTMF:
+    ContinuePlayDTMF();
+  }
+}
+
+std::vector<Session*> Call::sessions() {
+  std::vector<Session*> sessions;
+  MediaSessionMap::iterator it;
+  for (it = media_session_map_.begin(); it != media_session_map_.end(); ++it)
+    sessions.push_back(it->second.session);
+
+  return sessions;
+}
+
+bool Call::AddSession(Session* session, const SessionDescription* offer) {
+  bool succeeded = true;
+  MediaSession media_session;
+  media_session.session = session;
+  media_session.voice_channel = NULL;
+  media_session.video_channel = NULL;
+  media_session.data_channel = NULL;
+  media_session.recv_streams = NULL;
+
+  const ContentInfo* audio_offer = GetFirstAudioContent(offer);
+  const ContentInfo* video_offer = GetFirstVideoContent(offer);
+  const ContentInfo* data_offer = GetFirstDataContent(offer);
+  has_video_ = (video_offer != NULL);
+  has_data_ = (data_offer != NULL);
+
+  ASSERT(audio_offer != NULL);
+  // Create voice channel and start a media monitor.
+  media_session.voice_channel =
+      session_client_->channel_manager()->CreateVoiceChannel(
+          session, audio_offer->name, has_video_);
+  // voice_channel can be NULL in case of NullVoiceEngine.
+  if (media_session.voice_channel) {
+    media_session.voice_channel->SignalMediaMonitor.connect(
+        this, &Call::OnMediaMonitor);
+    media_session.voice_channel->StartMediaMonitor(kMediaMonitorInterval);
+  } else {
+    succeeded = false;
+  }
+
+  // If desired, create video channel and start a media monitor.
+  if (has_video_ && succeeded) {
+    media_session.video_channel =
+        session_client_->channel_manager()->CreateVideoChannel(
+            session,
+            video_offer->name,
+            true,
+            VideoOptions(),
+            media_session.voice_channel);
+    // video_channel can be NULL in case of NullVideoEngine.
+    if (media_session.video_channel) {
+      media_session.video_channel->SignalMediaMonitor.connect(
+          this, &Call::OnMediaMonitor);
+      media_session.video_channel->StartMediaMonitor(kMediaMonitorInterval);
+    } else {
+      succeeded = false;
+    }
+  }
+
+  // If desired, create data channel.
+  if (has_data_ && succeeded) {
+    const DataContentDescription* data = GetFirstDataContentDescription(offer);
+    if (data == NULL) {
+      succeeded = false;
+    } else {
+      DataChannelType data_channel_type = DCT_RTP;
+      if ((data->protocol() == kMediaProtocolSctp) ||
+          (data->protocol() == kMediaProtocolDtlsSctp)) {
+        data_channel_type = DCT_SCTP;
+      }
+
+      bool rtcp = false;
+      media_session.data_channel =
+          session_client_->channel_manager()->CreateDataChannel(
+              session, data_offer->name, rtcp, data_channel_type);
+      if (media_session.data_channel) {
+        media_session.data_channel->SignalDataReceived.connect(
+            this, &Call::OnDataReceived);
+      } else {
+        succeeded = false;
+      }
+    }
+  }
+
+  if (succeeded) {
+    // Add session to list, create channels for this session.
+    media_session.recv_streams = new MediaStreams;
+    media_session_map_[session->id()] = media_session;
+    session->SignalState.connect(this, &Call::OnSessionState);
+    session->SignalError.connect(this, &Call::OnSessionError);
+    session->SignalInfoMessage.connect(
+        this, &Call::OnSessionInfoMessage);
+    session->SignalRemoteDescriptionUpdate.connect(
+        this, &Call::OnRemoteDescriptionUpdate);
+    session->SignalReceivedTerminateReason
+      .connect(this, &Call::OnReceivedTerminateReason);
+
+    // If this call has the focus, enable this session's channels.
+    if (session_client_->GetFocus() == this) {
+      EnableSessionChannels(session, true);
+    }
+
+    // Signal client.
+    SignalAddSession(this, session);
+  }
+
+  return succeeded;
+}
+
+void Call::RemoveSession(Session* session) {
+  MediaSessionMap::iterator it = media_session_map_.find(session->id());
+  if (it == media_session_map_.end())
+    return;
+
+  // Remove all the screencasts, if they haven't been already.
+  while (!it->second.started_screencasts.empty()) {
+    uint32 ssrc = it->second.started_screencasts.begin()->first;
+    if (!StopScreencastWithoutSendingUpdate(it->second.session, ssrc)) {
+      LOG(LS_ERROR) << "Unable to stop screencast with ssrc " << ssrc;
+      ASSERT(false);
+    }
+  }
+
+  // Destroy video channel
+  VideoChannel* video_channel = it->second.video_channel;
+  if (video_channel != NULL)
+    session_client_->channel_manager()->DestroyVideoChannel(video_channel);
+
+  // Destroy voice channel
+  VoiceChannel* voice_channel = it->second.voice_channel;
+  if (voice_channel != NULL)
+    session_client_->channel_manager()->DestroyVoiceChannel(voice_channel);
+
+  // Destroy data channel
+  DataChannel* data_channel = it->second.data_channel;
+  if (data_channel != NULL)
+    session_client_->channel_manager()->DestroyDataChannel(data_channel);
+
+  delete it->second.recv_streams;
+  media_session_map_.erase(it);
+
+  // Destroy speaker monitor
+  StopSpeakerMonitor(session);
+
+  // Signal client
+  SignalRemoveSession(this, session);
+
+  // The call auto destroys when the last session is removed
+  rtc::Thread::Current()->Post(this, MSG_CHECKAUTODESTROY);
+}
+
+VoiceChannel* Call::GetVoiceChannel(Session* session) const {
+  MediaSessionMap::const_iterator it = media_session_map_.find(session->id());
+  return (it != media_session_map_.end()) ? it->second.voice_channel : NULL;
+}
+
+VideoChannel* Call::GetVideoChannel(Session* session) const {
+  MediaSessionMap::const_iterator it = media_session_map_.find(session->id());
+  return (it != media_session_map_.end()) ? it->second.video_channel : NULL;
+}
+
+DataChannel* Call::GetDataChannel(Session* session) const {
+  MediaSessionMap::const_iterator it = media_session_map_.find(session->id());
+  return (it != media_session_map_.end()) ? it->second.data_channel : NULL;
+}
+
+MediaStreams* Call::GetMediaStreams(Session* session) const {
+  MediaSessionMap::const_iterator it = media_session_map_.find(session->id());
+  return (it != media_session_map_.end()) ? it->second.recv_streams : NULL;
+}
+
+void Call::EnableChannels(bool enable) {
+  MediaSessionMap::iterator it;
+  for (it = media_session_map_.begin(); it != media_session_map_.end(); ++it) {
+    EnableSessionChannels(it->second.session, enable);
+  }
+}
+
+void Call::EnableSessionChannels(Session* session, bool enable) {
+  MediaSessionMap::iterator it = media_session_map_.find(session->id());
+  if (it == media_session_map_.end())
+    return;
+
+  VoiceChannel* voice_channel = it->second.voice_channel;
+  VideoChannel* video_channel = it->second.video_channel;
+  DataChannel* data_channel = it->second.data_channel;
+  if (voice_channel != NULL)
+    voice_channel->Enable(enable);
+  if (video_channel != NULL)
+    video_channel->Enable(enable);
+  if (data_channel != NULL)
+    data_channel->Enable(enable);
+}
+
+void Call::Mute(bool mute) {
+  muted_ = mute;
+  MediaSessionMap::iterator it;
+  for (it = media_session_map_.begin(); it != media_session_map_.end(); ++it) {
+    if (it->second.voice_channel != NULL)
+      it->second.voice_channel->MuteStream(0, mute);
+  }
+}
+
+void Call::MuteVideo(bool mute) {
+  video_muted_ = mute;
+  MediaSessionMap::iterator it;
+  for (it = media_session_map_.begin(); it != media_session_map_.end(); ++it) {
+    if (it->second.video_channel != NULL)
+      it->second.video_channel->MuteStream(0, mute);
+  }
+}
+
+bool Call::SendData(Session* session,
+                    const SendDataParams& params,
+                    const rtc::Buffer& payload,
+                    SendDataResult* result) {
+  DataChannel* data_channel = GetDataChannel(session);
+  if (!data_channel) {
+    LOG(LS_WARNING) << "Could not send data: no data channel.";
+    return false;
+  }
+
+  return data_channel->SendData(params, payload, result);
+}
+
+void Call::PressDTMF(int event) {
+  // Queue up this digit
+  if (queued_dtmf_.size() < kMaxDTMFDigits) {
+    LOG(LS_INFO) << "Call::PressDTMF(" << event << ")";
+
+    queued_dtmf_.push_back(event);
+
+    if (!playing_dtmf_) {
+      ContinuePlayDTMF();
+    }
+  }
+}
+
+cricket::VideoFormat ScreencastFormatFromFps(int fps) {
+  // The capturer pretty much ignore this, but just in case we give it
+  // a resolution big enough to cover any expected desktop.  In any
+  // case, it can't be 0x0, or the CaptureManager will fail to use it.
+  return cricket::VideoFormat(
+      1, 1,
+      cricket::VideoFormat::FpsToInterval(fps),
+      cricket::FOURCC_ANY);
+}
+
+bool Call::StartScreencast(Session* session,
+                           const std::string& streamid, uint32 ssrc,
+                           const ScreencastId& screenid, int fps) {
+  MediaSessionMap::iterator it = media_session_map_.find(session->id());
+  if (it == media_session_map_.end()) {
+    return false;
+  }
+
+  VideoChannel *video_channel = GetVideoChannel(session);
+  if (!video_channel) {
+    LOG(LS_WARNING) << "Cannot add screencast"
+                    << " because there is no video channel.";
+    return false;
+  }
+
+  VideoCapturer* capturer = session_client_->channel_manager()->
+      CreateScreenCapturer(screenid);
+  if (!capturer) {
+    LOG(LS_WARNING) << "Could not create screencast capturer.";
+    return false;
+  }
+
+  if (!video_channel->AddScreencast(ssrc, capturer)) {
+    delete capturer;
+    LOG(LS_WARNING) << "Could not add screencast capturer.";
+    return false;
+  }
+
+  VideoFormat format = ScreencastFormatFromFps(fps);
+  if (!session_client_->channel_manager()->StartVideoCapture(
+          capturer, format)) {
+    LOG(LS_WARNING) << "Could not start video capture.";
+    video_channel->RemoveScreencast(ssrc);
+    return false;
+  }
+
+  if (!video_channel->SetCapturer(ssrc, capturer)) {
+    LOG(LS_WARNING) << "Could not start sending screencast.";
+    session_client_->channel_manager()->StopVideoCapture(
+        capturer, ScreencastFormatFromFps(fps));
+    video_channel->RemoveScreencast(ssrc);
+  }
+
+  // TODO(pthatcher): Once the CaptureManager has a nicer interface
+  // for removing captures (such as having StartCapture return a
+  // handle), remove this StartedCapture stuff.
+  it->second.started_screencasts.insert(
+      std::make_pair(ssrc, StartedCapture(capturer, format)));
+
+  // TODO(pthatcher): Verify we aren't re-using an existing id or
+  // ssrc.
+  StreamParams stream;
+  stream.id = streamid;
+  stream.ssrcs.push_back(ssrc);
+  VideoContentDescription* video = CreateVideoStreamUpdate(stream);
+
+  // TODO(pthatcher): Wait until view request before sending video.
+  video_channel->SetLocalContent(video, CA_UPDATE, NULL);
+  SendVideoStreamUpdate(session, video);
+  return true;
+}
+
+bool Call::StopScreencast(Session* session,
+                          const std::string& streamid, uint32 ssrc) {
+  if (!StopScreencastWithoutSendingUpdate(session, ssrc)) {
+    return false;
+  }
+
+  VideoChannel *video_channel = GetVideoChannel(session);
+  if (!video_channel) {
+    LOG(LS_WARNING) << "Cannot add screencast"
+                    << " because there is no video channel.";
+    return false;
+  }
+
+  StreamParams stream;
+  stream.id = streamid;
+  // No ssrcs
+  VideoContentDescription* video = CreateVideoStreamUpdate(stream);
+
+  video_channel->SetLocalContent(video, CA_UPDATE, NULL);
+  SendVideoStreamUpdate(session, video);
+  return true;
+}
+
+bool Call::StopScreencastWithoutSendingUpdate(
+    Session* session, uint32 ssrc) {
+  MediaSessionMap::iterator it = media_session_map_.find(session->id());
+  if (it == media_session_map_.end()) {
+    return false;
+  }
+
+  VideoChannel *video_channel = GetVideoChannel(session);
+  if (!video_channel) {
+    LOG(LS_WARNING) << "Cannot remove screencast"
+                    << " because there is no video channel.";
+    return false;
+  }
+
+  StartedScreencastMap::const_iterator screencast_iter =
+      it->second.started_screencasts.find(ssrc);
+  if (screencast_iter == it->second.started_screencasts.end()) {
+    LOG(LS_WARNING) << "Could not stop screencast " << ssrc
+                    << " because there is no capturer.";
+    return false;
+  }
+
+  VideoCapturer* capturer = screencast_iter->second.capturer;
+  VideoFormat format = screencast_iter->second.format;
+  video_channel->SetCapturer(ssrc, NULL);
+  if (!session_client_->channel_manager()->StopVideoCapture(
+          capturer, format)) {
+    LOG(LS_WARNING) << "Could not stop screencast " << ssrc
+                    << " because could not stop capture.";
+    return false;
+  }
+  video_channel->RemoveScreencast(ssrc);
+  it->second.started_screencasts.erase(ssrc);
+  return true;
+}
+
+VideoContentDescription* Call::CreateVideoStreamUpdate(
+    const StreamParams& stream) {
+  VideoContentDescription* video = new VideoContentDescription();
+  video->set_multistream(true);
+  video->set_partial(true);
+  video->AddStream(stream);
+  return video;
+}
+
+void Call::SendVideoStreamUpdate(
+    Session* session, VideoContentDescription* video) {
+  // Takes the ownership of |video|.
+  rtc::scoped_ptr<VideoContentDescription> description(video);
+  const ContentInfo* video_info =
+      GetFirstVideoContent(session->local_description());
+  if (video_info == NULL) {
+    LOG(LS_WARNING) << "Cannot send stream update for video.";
+    return;
+  }
+
+  std::vector<ContentInfo> contents;
+  contents.push_back(
+      ContentInfo(video_info->name, video_info->type, description.get()));
+
+  session->SendDescriptionInfoMessage(contents);
+}
+
+void Call::ContinuePlayDTMF() {
+  playing_dtmf_ = false;
+
+  // Check to see if we have a queued tone
+  if (queued_dtmf_.size() > 0) {
+    playing_dtmf_ = true;
+
+    int tone = queued_dtmf_.front();
+    queued_dtmf_.pop_front();
+
+    LOG(LS_INFO) << "Call::ContinuePlayDTMF(" << tone << ")";
+    for (MediaSessionMap::iterator it = media_session_map_.begin();
+         it != media_session_map_.end(); ++it) {
+      if (it->second.voice_channel != NULL) {
+        it->second.voice_channel->PressDTMF(tone, true);
+      }
+    }
+
+    // Post a message to play the next tone or at least clear the playing_dtmf_
+    // bit.
+    rtc::Thread::Current()->PostDelayed(kDTMFDelay, this, MSG_PLAYDTMF);
+  }
+}
+
+void Call::Join(Call* call, bool enable) {
+  for (MediaSessionMap::iterator it = call->media_session_map_.begin();
+       it != call->media_session_map_.end(); ++it) {
+    // Shouldn't already exist.
+    ASSERT(media_session_map_.find(it->first) == media_session_map_.end());
+    media_session_map_[it->first] = it->second;
+
+    it->second.session->SignalState.connect(this, &Call::OnSessionState);
+    it->second.session->SignalError.connect(this, &Call::OnSessionError);
+    it->second.session->SignalReceivedTerminateReason
+      .connect(this, &Call::OnReceivedTerminateReason);
+
+    EnableSessionChannels(it->second.session, enable);
+  }
+
+  // Moved all the sessions over, so the other call should no longer have any.
+  call->media_session_map_.clear();
+}
+
+void Call::StartConnectionMonitor(Session* session, int cms) {
+  VoiceChannel* voice_channel = GetVoiceChannel(session);
+  if (voice_channel) {
+    voice_channel->SignalConnectionMonitor.connect(this,
+        &Call::OnConnectionMonitor);
+    voice_channel->StartConnectionMonitor(cms);
+  }
+
+  VideoChannel* video_channel = GetVideoChannel(session);
+  if (video_channel) {
+    video_channel->SignalConnectionMonitor.connect(this,
+        &Call::OnConnectionMonitor);
+    video_channel->StartConnectionMonitor(cms);
+  }
+}
+
+void Call::StopConnectionMonitor(Session* session) {
+  VoiceChannel* voice_channel = GetVoiceChannel(session);
+  if (voice_channel) {
+    voice_channel->StopConnectionMonitor();
+    voice_channel->SignalConnectionMonitor.disconnect(this);
+  }
+
+  VideoChannel* video_channel = GetVideoChannel(session);
+  if (video_channel) {
+    video_channel->StopConnectionMonitor();
+    video_channel->SignalConnectionMonitor.disconnect(this);
+  }
+}
+
+void Call::StartAudioMonitor(Session* session, int cms) {
+  VoiceChannel* voice_channel = GetVoiceChannel(session);
+  if (voice_channel) {
+    voice_channel->SignalAudioMonitor.connect(this, &Call::OnAudioMonitor);
+    voice_channel->StartAudioMonitor(cms);
+  }
+}
+
+void Call::StopAudioMonitor(Session* session) {
+  VoiceChannel* voice_channel = GetVoiceChannel(session);
+  if (voice_channel) {
+    voice_channel->StopAudioMonitor();
+    voice_channel->SignalAudioMonitor.disconnect(this);
+  }
+}
+
+bool Call::IsAudioMonitorRunning(Session* session) {
+  VoiceChannel* voice_channel = GetVoiceChannel(session);
+  if (voice_channel) {
+    return voice_channel->IsAudioMonitorRunning();
+  } else {
+    return false;
+  }
+}
+
+void Call::StartSpeakerMonitor(Session* session) {
+  if (speaker_monitor_map_.find(session->id()) == speaker_monitor_map_.end()) {
+    if (!IsAudioMonitorRunning(session)) {
+      StartAudioMonitor(session, kAudioMonitorPollPeriodMillis);
+    }
+    CurrentSpeakerMonitor* speaker_monitor =
+        new cricket::CurrentSpeakerMonitor(
+            audio_source_proxy_.get(), session);
+    speaker_monitor->SignalUpdate.connect(this, &Call::OnSpeakerMonitor);
+    speaker_monitor->Start();
+    speaker_monitor_map_[session->id()] = speaker_monitor;
+  } else {
+    LOG(LS_WARNING) << "Already started speaker monitor for session "
+                    << session->id() << ".";
+  }
+}
+
+void Call::StopSpeakerMonitor(Session* session) {
+  if (speaker_monitor_map_.find(session->id()) == speaker_monitor_map_.end()) {
+    LOG(LS_WARNING) << "Speaker monitor for session "
+                    << session->id() << " already stopped.";
+  } else {
+    CurrentSpeakerMonitor* monitor = speaker_monitor_map_[session->id()];
+    monitor->Stop();
+    speaker_monitor_map_.erase(session->id());
+    delete monitor;
+  }
+}
+
+void Call::OnConnectionMonitor(VoiceChannel* channel,
+                               const std::vector<ConnectionInfo> &infos) {
+  SignalConnectionMonitor(this, infos);
+}
+
+void Call::OnMediaMonitor(VoiceChannel* channel, const VoiceMediaInfo& info) {
+  last_voice_media_info_ = info;
+  SignalMediaMonitor(this, info);
+}
+
+void Call::OnAudioMonitor(VoiceChannel* channel, const AudioInfo& info) {
+  SignalAudioMonitor(this, info);
+}
+
+void Call::OnSpeakerMonitor(CurrentSpeakerMonitor* monitor, uint32 ssrc) {
+  Session* session = static_cast<Session*>(monitor->session());
+  MediaStreams* recv_streams = GetMediaStreams(session);
+  if (recv_streams) {
+    StreamParams stream;
+    recv_streams->GetAudioStream(StreamSelector(ssrc), &stream);
+    SignalSpeakerMonitor(this, session, stream);
+  }
+}
+
+void Call::OnConnectionMonitor(VideoChannel* channel,
+                               const std::vector<ConnectionInfo> &infos) {
+  SignalVideoConnectionMonitor(this, infos);
+}
+
+void Call::OnMediaMonitor(VideoChannel* channel, const VideoMediaInfo& info) {
+  SignalVideoMediaMonitor(this, info);
+}
+
+void Call::OnDataReceived(DataChannel* channel,
+                          const ReceiveDataParams& params,
+                          const rtc::Buffer& payload) {
+  SignalDataReceived(this, params, payload);
+}
+
+uint32 Call::id() {
+  return id_;
+}
+
+void Call::OnSessionState(BaseSession* base_session, BaseSession::State state) {
+  Session* session = static_cast<Session*>(base_session);
+  switch (state) {
+    case Session::STATE_RECEIVEDACCEPT:
+      UpdateRemoteMediaStreams(session,
+          session->remote_description()->contents(), false);
+      session_client_->session_manager()->signaling_thread()->Clear(this,
+          MSG_TERMINATECALL);
+      break;
+    case Session::STATE_RECEIVEDREJECT:
+    case Session::STATE_RECEIVEDTERMINATE:
+      session_client_->session_manager()->signaling_thread()->Clear(this,
+          MSG_TERMINATECALL);
+      break;
+    default:
+      break;
+  }
+  SignalSessionState(this, session, state);
+}
+
+void Call::OnSessionError(BaseSession* base_session, Session::Error error) {
+  session_client_->session_manager()->signaling_thread()->Clear(this,
+      MSG_TERMINATECALL);
+  SignalSessionError(this, static_cast<Session*>(base_session), error);
+}
+
+void Call::OnSessionInfoMessage(Session* session,
+                                const buzz::XmlElement* action_elem) {
+  if (!IsJingleViewRequest(action_elem)) {
+    return;
+  }
+
+  ViewRequest view_request;
+  ParseError error;
+  if (!ParseJingleViewRequest(action_elem, &view_request, &error)) {
+    LOG(LS_WARNING) << "Failed to parse view request: " << error.text;
+    return;
+  }
+
+  VideoChannel* video_channel = GetVideoChannel(session);
+  if (video_channel == NULL) {
+    LOG(LS_WARNING) << "Ignore view request since we have no video channel.";
+    return;
+  }
+
+  if (!video_channel->ApplyViewRequest(view_request)) {
+    LOG(LS_WARNING) << "Failed to ApplyViewRequest.";
+  }
+}
+
+void Call::OnRemoteDescriptionUpdate(BaseSession* base_session,
+                                     const ContentInfos& updated_contents) {
+  Session* session = static_cast<Session*>(base_session);
+
+  const ContentInfo* audio_content = GetFirstAudioContent(updated_contents);
+  if (audio_content) {
+    const AudioContentDescription* audio_update =
+        static_cast<const AudioContentDescription*>(audio_content->description);
+    if (!audio_update->codecs().empty()) {
+      UpdateVoiceChannelRemoteContent(session, audio_update);
+    }
+  }
+
+  const ContentInfo* video_content = GetFirstVideoContent(updated_contents);
+  if (video_content) {
+    const VideoContentDescription* video_update =
+        static_cast<const VideoContentDescription*>(video_content->description);
+    if (!video_update->codecs().empty()) {
+      UpdateVideoChannelRemoteContent(session, video_update);
+    }
+  }
+
+  const ContentInfo* data_content = GetFirstDataContent(updated_contents);
+  if (data_content) {
+    const DataContentDescription* data_update =
+        static_cast<const DataContentDescription*>(data_content->description);
+    if (!data_update->codecs().empty()) {
+      UpdateDataChannelRemoteContent(session, data_update);
+    }
+  }
+
+  UpdateRemoteMediaStreams(session, updated_contents, true);
+}
+
+bool Call::UpdateVoiceChannelRemoteContent(
+    Session* session, const AudioContentDescription* audio) {
+  VoiceChannel* voice_channel = GetVoiceChannel(session);
+  if (!voice_channel->SetRemoteContent(audio, CA_UPDATE, NULL)) {
+    const std::string error_desc =
+        "Failure in audio SetRemoteContent with CA_UPDATE";
+    LOG(LS_ERROR) << error_desc;
+    session->SetError(BaseSession::ERROR_CONTENT, error_desc);
+    return false;
+  }
+  return true;
+}
+
+bool Call::UpdateVideoChannelRemoteContent(
+    Session* session, const VideoContentDescription* video) {
+  VideoChannel* video_channel = GetVideoChannel(session);
+  if (!video_channel->SetRemoteContent(video, CA_UPDATE, NULL)) {
+    const std::string error_desc =
+        "Failure in video SetRemoteContent with CA_UPDATE";
+    LOG(LS_ERROR) << error_desc;
+    session->SetError(BaseSession::ERROR_CONTENT, error_desc);
+    return false;
+  }
+  return true;
+}
+
+bool Call::UpdateDataChannelRemoteContent(
+    Session* session, const DataContentDescription* data) {
+  DataChannel* data_channel = GetDataChannel(session);
+  if (!data_channel->SetRemoteContent(data, CA_UPDATE, NULL)) {
+    const std::string error_desc =
+        "Failure in data SetRemoteContent with CA_UPDATE";
+    LOG(LS_ERROR) << error_desc;
+    session->SetError(BaseSession::ERROR_CONTENT, error_desc);
+    return false;
+  }
+  return true;
+}
+
+void Call::UpdateRemoteMediaStreams(Session* session,
+                                    const ContentInfos& updated_contents,
+                                    bool update_channels) {
+  MediaStreams* recv_streams = GetMediaStreams(session);
+  if (!recv_streams)
+    return;
+
+  cricket::MediaStreams added_streams;
+  cricket::MediaStreams removed_streams;
+
+  const ContentInfo* audio_content = GetFirstAudioContent(updated_contents);
+  if (audio_content) {
+    const AudioContentDescription* audio_update =
+        static_cast<const AudioContentDescription*>(audio_content->description);
+    UpdateRecvStreams(audio_update->streams(),
+                      update_channels ? GetVoiceChannel(session) : NULL,
+                      recv_streams->mutable_audio(),
+                      added_streams.mutable_audio(),
+                      removed_streams.mutable_audio());
+  }
+
+  const ContentInfo* video_content = GetFirstVideoContent(updated_contents);
+  if (video_content) {
+    const VideoContentDescription* video_update =
+        static_cast<const VideoContentDescription*>(video_content->description);
+    UpdateRecvStreams(video_update->streams(),
+                      update_channels ? GetVideoChannel(session) : NULL,
+                      recv_streams->mutable_video(),
+                      added_streams.mutable_video(),
+                      removed_streams.mutable_video());
+  }
+
+  const ContentInfo* data_content = GetFirstDataContent(updated_contents);
+  if (data_content) {
+    const DataContentDescription* data_update =
+        static_cast<const DataContentDescription*>(data_content->description);
+    UpdateRecvStreams(data_update->streams(),
+                      update_channels ? GetDataChannel(session) : NULL,
+                      recv_streams->mutable_data(),
+                      added_streams.mutable_data(),
+                      removed_streams.mutable_data());
+  }
+
+  if (!added_streams.empty() || !removed_streams.empty()) {
+    SignalMediaStreamsUpdate(this, session, added_streams, removed_streams);
+  }
+}
+
+void FindStreamChanges(const std::vector<StreamParams>& streams,
+                       const std::vector<StreamParams>& updates,
+                       std::vector<StreamParams>* added_streams,
+                       std::vector<StreamParams>* removed_streams) {
+  for (std::vector<StreamParams>::const_iterator update = updates.begin();
+       update != updates.end(); ++update) {
+    StreamParams stream;
+    if (GetStreamByIds(streams, update->groupid, update->id, &stream)) {
+      if (!update->has_ssrcs()) {
+        removed_streams->push_back(stream);
+      }
+    } else {
+      // There's a bug on reflector that will send <stream>s even
+      // though there is not ssrc (which means there isn't really a
+      // stream).  To work around it, we simply ignore new <stream>s
+      // that don't have any ssrcs.
+      if (update->has_ssrcs()) {
+        added_streams->push_back(*update);
+      }
+    }
+  }
+}
+
+void Call::UpdateRecvStreams(const std::vector<StreamParams>& update_streams,
+                             BaseChannel* channel,
+                             std::vector<StreamParams>* recv_streams,
+                             std::vector<StreamParams>* added_streams,
+                             std::vector<StreamParams>* removed_streams) {
+  FindStreamChanges(*recv_streams,
+                    update_streams, added_streams, removed_streams);
+  AddRecvStreams(*added_streams,
+                 channel, recv_streams);
+  RemoveRecvStreams(*removed_streams,
+                    channel, recv_streams);
+}
+
+void Call::AddRecvStreams(const std::vector<StreamParams>& added_streams,
+                          BaseChannel* channel,
+                          std::vector<StreamParams>* recv_streams) {
+  std::vector<StreamParams>::const_iterator stream;
+  for (stream = added_streams.begin();
+       stream != added_streams.end();
+       ++stream) {
+    AddRecvStream(*stream, channel, recv_streams);
+  }
+}
+
+void Call::AddRecvStream(const StreamParams& stream,
+                         BaseChannel* channel,
+                         std::vector<StreamParams>* recv_streams) {
+  if (channel && stream.has_ssrcs()) {
+    channel->AddRecvStream(stream);
+  }
+  recv_streams->push_back(stream);
+}
+
+void Call::RemoveRecvStreams(const std::vector<StreamParams>& removed_streams,
+                             BaseChannel* channel,
+                             std::vector<StreamParams>* recv_streams) {
+  std::vector<StreamParams>::const_iterator stream;
+  for (stream = removed_streams.begin();
+       stream != removed_streams.end();
+       ++stream) {
+    RemoveRecvStream(*stream, channel, recv_streams);
+  }
+}
+
+void Call::RemoveRecvStream(const StreamParams& stream,
+                            BaseChannel* channel,
+                            std::vector<StreamParams>* recv_streams) {
+  if (channel && stream.has_ssrcs()) {
+    // TODO(pthatcher): Change RemoveRecvStream to take a stream argument.
+    channel->RemoveRecvStream(stream.first_ssrc());
+  }
+  RemoveStreamByIds(recv_streams, stream.groupid, stream.id);
+}
+
+void Call::OnReceivedTerminateReason(Session* session,
+                                     const std::string& reason) {
+  session_client_->session_manager()->signaling_thread()->Clear(this,
+    MSG_TERMINATECALL);
+  SignalReceivedTerminateReason(this, session, reason);
+}
+
+// TODO(mdodd): Get ride of this method since all Hangouts are using a secure
+// connection.
+bool Call::secure() const {
+  if (session_client_->secure() == SEC_DISABLED) {
+    return false;
+  }
+
+  bool ret = true;
+  int i = 0;
+
+  MediaSessionMap::const_iterator it;
+  for (it = media_session_map_.begin(); it != media_session_map_.end(); ++it) {
+    LOG_F(LS_VERBOSE) << "session[" << i
+                      << "], check local and remote descriptions";
+    i++;
+
+    if (!SessionDescriptionContainsCrypto(
+            it->second.session->local_description()) ||
+        !SessionDescriptionContainsCrypto(
+            it->second.session->remote_description())) {
+      ret = false;
+      break;
+    }
+  }
+
+  LOG_F(LS_VERBOSE) << "secure=" << ret;
+  return ret;
+}
+
+bool Call::SessionDescriptionContainsCrypto(
+    const SessionDescription* sdesc) const {
+  if (sdesc == NULL) {
+    LOG_F(LS_VERBOSE) << "sessionDescription is NULL";
+    return false;
+  }
+
+  return ContentContainsCrypto(sdesc->GetContentByName(CN_AUDIO)) &&
+         ContentContainsCrypto(sdesc->GetContentByName(CN_VIDEO));
+}
+
+Session* Call::InternalInitiateSession(const std::string& id,
+                                       const buzz::Jid& to,
+                                       const std::string& initiator_name,
+                                       const CallOptions& options) {
+  const SessionDescription* offer = session_client_->CreateOffer(options);
+
+  Session* session = session_client_->CreateSession(id, this);
+  // Only override the initiator_name if it was manually supplied. Otherwise,
+  // session_client_ will supply the local jid as initiator in CreateOffer.
+  if (!initiator_name.empty()) {
+    session->set_initiator_name(initiator_name);
+  }
+
+  AddSession(session, offer);
+  session->Initiate(to.Str(), offer);
+
+  // After this timeout, terminate the call because the callee isn't
+  // answering
+  session_client_->session_manager()->signaling_thread()->Clear(this,
+      MSG_TERMINATECALL);
+  session_client_->session_manager()->signaling_thread()->PostDelayed(
+    send_to_voicemail_ ? kSendToVoicemailTimeout : kNoVoicemailTimeout,
+    this, MSG_TERMINATECALL);
+  return session;
+}
+
+AudioSourceProxy* Call::GetAudioSourceProxy() {
+  return audio_source_proxy_.get();
+}
+
+}  // namespace cricket
diff --git a/libjingle/media/call.h b/libjingle/media/call.h
new file mode 100644
index 0000000..f7bd57e
--- /dev/null
+++ b/libjingle/media/call.h
@@ -0,0 +1,307 @@
+/*
+ * 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.
+ */
+
+#ifndef TALK_SESSION_MEDIA_CALL_H_
+#define TALK_SESSION_MEDIA_CALL_H_
+
+#include <deque>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "talk/media/base/mediachannel.h"
+#include "talk/media/base/screencastid.h"
+#include "talk/media/base/streamparams.h"
+#include "talk/media/base/videocommon.h"
+#include "webrtc/p2p/base/session.h"
+#include "webrtc/p2p/client/socketmonitor.h"
+#include "talk/session/media/audiomonitor.h"
+#include "talk/session/media/currentspeakermonitor.h"
+#include "webrtc/libjingle/media/mediamessages.h"
+#include "talk/session/media/mediasession.h"
+#include "webrtc/libjingle/xmpp/jid.h"
+#include "webrtc/base/messagequeue.h"
+
+namespace cricket {
+
+struct AudioInfo;
+class Call;
+class MediaSessionClient;
+class BaseChannel;
+class VoiceChannel;
+class VideoChannel;
+class DataChannel;
+
+// Can't typedef this easily since it's forward declared as struct elsewhere.
+struct CallOptions : public MediaSessionOptions {
+};
+
+// CurrentSpeakerMonitor used to have a dependency on Call. To remove this
+// dependency, we create AudioSourceContext. CurrentSpeakerMonitor depends on
+// AudioSourceContext.
+// AudioSourceProxy acts as a proxy so that when SignalAudioMonitor
+// in Call is triggered, SignalAudioMonitor in AudioSourceContext is triggered.
+// Likewise, when OnMediaStreamsUpdate in Call is triggered,
+// OnMediaStreamsUpdate in AudioSourceContext is triggered.
+class AudioSourceProxy: public AudioSourceContext, public sigslot::has_slots<> {
+ public:
+  explicit AudioSourceProxy(Call* call);
+
+ private:
+  void OnAudioMonitor(Call* call, const AudioInfo& info);
+  void OnMediaStreamsUpdate(Call* call, cricket::Session*,
+      const cricket::MediaStreams&, const cricket::MediaStreams&);
+
+  Call* call_;
+};
+
+class Call : public rtc::MessageHandler, public sigslot::has_slots<> {
+ public:
+  explicit Call(MediaSessionClient* session_client);
+  ~Call();
+
+  // |initiator| can be empty.
+  Session* InitiateSession(const buzz::Jid& to, const buzz::Jid& initiator,
+                           const CallOptions& options);
+  Session* InitiateSession(const std::string& id, const buzz::Jid& to,
+                           const CallOptions& options);
+  void AcceptSession(Session* session, const CallOptions& options);
+  void RejectSession(Session* session);
+  void TerminateSession(Session* session);
+  void Terminate();
+  bool SendViewRequest(Session* session,
+                       const ViewRequest& view_request);
+  void SetVideoRenderer(Session* session, uint32 ssrc,
+                        VideoRenderer* renderer);
+  void StartConnectionMonitor(Session* session, int cms);
+  void StopConnectionMonitor(Session* session);
+  void StartAudioMonitor(Session* session, int cms);
+  void StopAudioMonitor(Session* session);
+  bool IsAudioMonitorRunning(Session* session);
+  void StartSpeakerMonitor(Session* session);
+  void StopSpeakerMonitor(Session* session);
+  void Mute(bool mute);
+  void MuteVideo(bool mute);
+  bool SendData(Session* session,
+                const SendDataParams& params,
+                const rtc::Buffer& payload,
+                SendDataResult* result);
+  void PressDTMF(int event);
+  bool StartScreencast(Session* session,
+                       const std::string& stream_name, uint32 ssrc,
+                       const ScreencastId& screenid, int fps);
+  bool StopScreencast(Session* session,
+                      const std::string& stream_name, uint32 ssrc);
+
+  std::vector<Session*> sessions();
+  uint32 id();
+  bool has_video() const { return has_video_; }
+  bool has_data() const { return has_data_; }
+  bool muted() const { return muted_; }
+  bool video() const { return has_video_; }
+  bool secure() const;
+  bool video_muted() const { return video_muted_; }
+  const std::vector<StreamParams>* GetDataRecvStreams(Session* session) const {
+    MediaStreams* recv_streams = GetMediaStreams(session);
+    return recv_streams ? &recv_streams->data() : NULL;
+  }
+  const std::vector<StreamParams>* GetVideoRecvStreams(Session* session) const {
+    MediaStreams* recv_streams = GetMediaStreams(session);
+    return recv_streams ? &recv_streams->video() : NULL;
+  }
+  const std::vector<StreamParams>* GetAudioRecvStreams(Session* session) const {
+    MediaStreams* recv_streams = GetMediaStreams(session);
+    return recv_streams ? &recv_streams->audio() : NULL;
+  }
+  VoiceChannel* GetVoiceChannel(Session* session) const;
+  VideoChannel* GetVideoChannel(Session* session) const;
+  DataChannel* GetDataChannel(Session* session) const;
+  // Public just for unit tests
+  VideoContentDescription* CreateVideoStreamUpdate(const StreamParams& stream);
+  // Takes ownership of video.
+  void SendVideoStreamUpdate(Session* session, VideoContentDescription* video);
+
+  // Setting this to false will cause the call to have a longer timeout and
+  // for the SignalSetupToCallVoicemail to never fire.
+  void set_send_to_voicemail(bool send_to_voicemail) {
+    send_to_voicemail_ = send_to_voicemail;
+  }
+  bool send_to_voicemail() { return send_to_voicemail_; }
+  const VoiceMediaInfo& last_voice_media_info() const {
+    return last_voice_media_info_;
+  }
+
+  // Sets a flag on the chatapp that will redirect the call to voicemail once
+  // the call has been terminated
+  sigslot::signal0<> SignalSetupToCallVoicemail;
+  sigslot::signal2<Call*, Session*> SignalAddSession;
+  sigslot::signal2<Call*, Session*> SignalRemoveSession;
+  sigslot::signal3<Call*, Session*, Session::State>
+      SignalSessionState;
+  sigslot::signal3<Call*, Session*, Session::Error>
+      SignalSessionError;
+  sigslot::signal3<Call*, Session*, const std::string &>
+      SignalReceivedTerminateReason;
+  sigslot::signal2<Call*, const std::vector<ConnectionInfo> &>
+      SignalConnectionMonitor;
+  sigslot::signal2<Call*, const VoiceMediaInfo&> SignalMediaMonitor;
+  sigslot::signal2<Call*, const AudioInfo&> SignalAudioMonitor;
+  // Empty nick on StreamParams means "unknown".
+  // No ssrcs in StreamParams means "no current speaker".
+  sigslot::signal3<Call*,
+                   Session*,
+                   const StreamParams&> SignalSpeakerMonitor;
+  sigslot::signal2<Call*, const std::vector<ConnectionInfo> &>
+      SignalVideoConnectionMonitor;
+  sigslot::signal2<Call*, const VideoMediaInfo&> SignalVideoMediaMonitor;
+  // Gives added streams and removed streams, in that order.
+  sigslot::signal4<Call*,
+                   Session*,
+                   const MediaStreams&,
+                   const MediaStreams&> SignalMediaStreamsUpdate;
+  sigslot::signal3<Call*,
+                   const ReceiveDataParams&,
+                   const rtc::Buffer&> SignalDataReceived;
+
+  AudioSourceProxy* GetAudioSourceProxy();
+
+ private:
+  void OnMessage(rtc::Message* message);
+  void OnSessionState(BaseSession* base_session, BaseSession::State state);
+  void OnSessionError(BaseSession* base_session, Session::Error error);
+  void OnSessionInfoMessage(
+      Session* session, const buzz::XmlElement* action_elem);
+  void OnViewRequest(
+      Session* session, const ViewRequest& view_request);
+  void OnRemoteDescriptionUpdate(
+      BaseSession* base_session, const ContentInfos& updated_contents);
+  void OnReceivedTerminateReason(Session* session, const std::string &reason);
+  void IncomingSession(Session* session, const SessionDescription* offer);
+  // Returns true on success.
+  bool AddSession(Session* session, const SessionDescription* offer);
+  void RemoveSession(Session* session);
+  void EnableChannels(bool enable);
+  void EnableSessionChannels(Session* session, bool enable);
+  void Join(Call* call, bool enable);
+  void OnConnectionMonitor(VoiceChannel* channel,
+                           const std::vector<ConnectionInfo> &infos);
+  void OnMediaMonitor(VoiceChannel* channel, const VoiceMediaInfo& info);
+  void OnAudioMonitor(VoiceChannel* channel, const AudioInfo& info);
+  void OnSpeakerMonitor(CurrentSpeakerMonitor* monitor, uint32 ssrc);
+  void OnConnectionMonitor(VideoChannel* channel,
+                           const std::vector<ConnectionInfo> &infos);
+  void OnMediaMonitor(VideoChannel* channel, const VideoMediaInfo& info);
+  void OnDataReceived(DataChannel* channel,
+                      const ReceiveDataParams& params,
+                      const rtc::Buffer& payload);
+  MediaStreams* GetMediaStreams(Session* session) const;
+  void UpdateRemoteMediaStreams(Session* session,
+                                const ContentInfos& updated_contents,
+                                bool update_channels);
+  bool UpdateVoiceChannelRemoteContent(Session* session,
+                                       const AudioContentDescription* audio);
+  bool UpdateVideoChannelRemoteContent(Session* session,
+                                       const VideoContentDescription* video);
+  bool UpdateDataChannelRemoteContent(Session* session,
+                                      const DataContentDescription* data);
+  void UpdateRecvStreams(const std::vector<StreamParams>& update_streams,
+                         BaseChannel* channel,
+                         std::vector<StreamParams>* recv_streams,
+                         std::vector<StreamParams>* added_streams,
+                         std::vector<StreamParams>* removed_streams);
+  void AddRecvStreams(const std::vector<StreamParams>& added_streams,
+                      BaseChannel* channel,
+                      std::vector<StreamParams>* recv_streams);
+  void AddRecvStream(const StreamParams& stream,
+                     BaseChannel* channel,
+                     std::vector<StreamParams>* recv_streams);
+  void RemoveRecvStreams(const std::vector<StreamParams>& removed_streams,
+                         BaseChannel* channel,
+                         std::vector<StreamParams>* recv_streams);
+  void RemoveRecvStream(const StreamParams& stream,
+                        BaseChannel* channel,
+                        std::vector<StreamParams>* recv_streams);
+  void ContinuePlayDTMF();
+  bool StopScreencastWithoutSendingUpdate(Session* session, uint32 ssrc);
+  bool StopAllScreencastsWithoutSendingUpdate(Session* session);
+  bool SessionDescriptionContainsCrypto(const SessionDescription* sdesc) const;
+  Session* InternalInitiateSession(const std::string& id,
+                                   const buzz::Jid& to,
+                                   const std::string& initiator_name,
+                                   const CallOptions& options);
+
+  uint32 id_;
+  MediaSessionClient* session_client_;
+
+  struct StartedCapture {
+    StartedCapture(cricket::VideoCapturer* capturer,
+                   const cricket::VideoFormat& format) :
+        capturer(capturer),
+        format(format) {
+    }
+    cricket::VideoCapturer* capturer;
+    cricket::VideoFormat format;
+  };
+  typedef std::map<uint32, StartedCapture> StartedScreencastMap;
+
+  struct MediaSession {
+    Session* session;
+    VoiceChannel* voice_channel;
+    VideoChannel* video_channel;
+    DataChannel* data_channel;
+    MediaStreams* recv_streams;
+    StartedScreencastMap started_screencasts;
+  };
+
+  // Create a map of media sessions, keyed off session->id().
+  typedef std::map<std::string, MediaSession> MediaSessionMap;
+  MediaSessionMap media_session_map_;
+
+  std::map<std::string, CurrentSpeakerMonitor*> speaker_monitor_map_;
+  bool has_video_;
+  bool has_data_;
+  bool muted_;
+  bool video_muted_;
+  bool send_to_voicemail_;
+
+  // DTMF tones have to be queued up so that we don't flood the call.  We
+  // keep a deque (doubely ended queue) of them around.  While one is playing we
+  // set the playing_dtmf_ bit and schedule a message in XX msec to clear that
+  // bit or start the next tone playing.
+  std::deque<int> queued_dtmf_;
+  bool playing_dtmf_;
+
+  VoiceMediaInfo last_voice_media_info_;
+
+  rtc::scoped_ptr<AudioSourceProxy> audio_source_proxy_;
+
+  friend class MediaSessionClient;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_MEDIA_CALL_H_
diff --git a/libjingle/media/mediamessages.cc b/libjingle/media/mediamessages.cc
new file mode 100644
index 0000000..14bf2f6
--- /dev/null
+++ b/libjingle/media/mediamessages.cc
@@ -0,0 +1,387 @@
+/*
+ * libjingle
+ * Copyright 2010 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.
+ */
+
+/*
+ * Documentation is in mediamessages.h.
+ */
+
+#include "webrtc/libjingle/media/mediamessages.h"
+
+#include "webrtc/p2p/base/constants.h"
+#include "webrtc/libjingle/session/parsing.h"
+#include "webrtc/libjingle/media/mediasessionclient.h"
+#include "webrtc/libjingle/xmllite/xmlelement.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/stringencode.h"
+
+namespace cricket {
+
+namespace {
+
+// NOTE: There is no check here for duplicate streams, so check before
+// adding.
+void AddStream(std::vector<StreamParams>* streams, const StreamParams& stream) {
+  streams->push_back(stream);
+}
+
+bool ParseSsrc(const std::string& string, uint32* ssrc) {
+  return rtc::FromString(string, ssrc);
+}
+
+// Builds a <view> element according to the following spec:
+// goto/jinglemuc
+buzz::XmlElement* CreateViewElem(const std::string& name,
+                                 const std::string& type) {
+  buzz::XmlElement* view_elem =
+      new buzz::XmlElement(QN_JINGLE_DRAFT_VIEW, true);
+  view_elem->AddAttr(QN_NAME, name);
+  view_elem->SetAttr(QN_TYPE, type);
+  return view_elem;
+}
+
+buzz::XmlElement* CreateVideoViewElem(const std::string& content_name,
+                                      const std::string& type) {
+  return CreateViewElem(content_name, type);
+}
+
+buzz::XmlElement* CreateNoneVideoViewElem(const std::string& content_name) {
+  return CreateVideoViewElem(content_name, STR_JINGLE_DRAFT_VIEW_TYPE_NONE);
+}
+
+buzz::XmlElement* CreateStaticVideoViewElem(const std::string& content_name,
+                                            const StaticVideoView& view) {
+  buzz::XmlElement* view_elem =
+      CreateVideoViewElem(content_name, STR_JINGLE_DRAFT_VIEW_TYPE_STATIC);
+  AddXmlAttr(view_elem, QN_SSRC, view.selector.ssrc);
+
+  buzz::XmlElement* params_elem = new buzz::XmlElement(QN_JINGLE_DRAFT_PARAMS);
+  AddXmlAttr(params_elem, QN_WIDTH, view.width);
+  AddXmlAttr(params_elem, QN_HEIGHT, view.height);
+  AddXmlAttr(params_elem, QN_FRAMERATE, view.framerate);
+  AddXmlAttr(params_elem, QN_PREFERENCE, view.preference);
+  view_elem->AddElement(params_elem);
+
+  return view_elem;
+}
+
+}  //  namespace
+
+bool MediaStreams::GetAudioStream(
+    const StreamSelector& selector, StreamParams* stream) {
+  return GetStream(audio_, selector, stream);
+}
+
+bool MediaStreams::GetVideoStream(
+    const StreamSelector& selector, StreamParams* stream) {
+  return GetStream(video_, selector, stream);
+}
+
+bool MediaStreams::GetDataStream(
+    const StreamSelector& selector, StreamParams* stream) {
+  return GetStream(data_, selector, stream);
+}
+
+void MediaStreams::CopyFrom(const MediaStreams& streams) {
+  audio_ = streams.audio_;
+  video_ = streams.video_;
+  data_ = streams.data_;
+}
+
+void MediaStreams::AddAudioStream(const StreamParams& stream) {
+  AddStream(&audio_, stream);
+}
+
+void MediaStreams::AddVideoStream(const StreamParams& stream) {
+  AddStream(&video_, stream);
+}
+
+void MediaStreams::AddDataStream(const StreamParams& stream) {
+  AddStream(&data_, stream);
+}
+
+bool MediaStreams::RemoveAudioStream(
+    const StreamSelector& selector) {
+  return RemoveStream(&audio_, selector);
+}
+
+bool MediaStreams::RemoveVideoStream(
+    const StreamSelector& selector) {
+  return RemoveStream(&video_, selector);
+}
+
+bool MediaStreams::RemoveDataStream(
+    const StreamSelector& selector) {
+  return RemoveStream(&data_, selector);
+}
+
+bool IsJingleViewRequest(const buzz::XmlElement* action_elem) {
+  return action_elem->FirstNamed(QN_JINGLE_DRAFT_VIEW) != NULL;
+}
+
+bool ParseStaticVideoView(const buzz::XmlElement* view_elem,
+                          StaticVideoView* view,
+                          ParseError* error) {
+  uint32 ssrc;
+  if (!ParseSsrc(view_elem->Attr(QN_SSRC), &ssrc)) {
+    return BadParse("Invalid or missing view ssrc.", error);
+  }
+  view->selector = StreamSelector(ssrc);
+
+  const buzz::XmlElement* params_elem =
+      view_elem->FirstNamed(QN_JINGLE_DRAFT_PARAMS);
+  if (params_elem) {
+    view->width = GetXmlAttr(params_elem, QN_WIDTH, 0);
+    view->height = GetXmlAttr(params_elem, QN_HEIGHT, 0);
+    view->framerate = GetXmlAttr(params_elem, QN_FRAMERATE, 0);
+    view->preference = GetXmlAttr(params_elem, QN_PREFERENCE, 0);
+  } else {
+    return BadParse("Missing view params.", error);
+  }
+
+  return true;
+}
+
+bool ParseJingleViewRequest(const buzz::XmlElement* action_elem,
+                            ViewRequest* view_request,
+                            ParseError* error) {
+  for (const buzz::XmlElement* view_elem =
+           action_elem->FirstNamed(QN_JINGLE_DRAFT_VIEW);
+       view_elem != NULL;
+       view_elem = view_elem->NextNamed(QN_JINGLE_DRAFT_VIEW)) {
+    std::string type = view_elem->Attr(QN_TYPE);
+    if (STR_JINGLE_DRAFT_VIEW_TYPE_NONE == type) {
+      view_request->static_video_views.clear();
+      return true;
+    } else if (STR_JINGLE_DRAFT_VIEW_TYPE_STATIC == type) {
+      StaticVideoView static_video_view(StreamSelector(0), 0, 0, 0);
+      if (!ParseStaticVideoView(view_elem, &static_video_view, error)) {
+        return false;
+      }
+      view_request->static_video_views.push_back(static_video_view);
+    } else {
+      LOG(LS_INFO) << "Ingnoring unknown view type: " << type;
+    }
+  }
+  return true;
+}
+
+bool WriteJingleViewRequest(const std::string& content_name,
+                            const ViewRequest& request,
+                            XmlElements* elems,
+                            WriteError* error) {
+  if (request.static_video_views.empty()) {
+    elems->push_back(CreateNoneVideoViewElem(content_name));
+  } else {
+    for (StaticVideoViews::const_iterator view =
+             request.static_video_views.begin();
+         view != request.static_video_views.end(); ++view) {
+      elems->push_back(CreateStaticVideoViewElem(content_name, *view));
+    }
+  }
+  return true;
+}
+
+bool ParseSsrcAsLegacyStream(const buzz::XmlElement* desc_elem,
+                             std::vector<StreamParams>* streams,
+                             ParseError* error) {
+  const std::string ssrc_str = desc_elem->Attr(QN_SSRC);
+  if (!ssrc_str.empty()) {
+    uint32 ssrc;
+    if (!ParseSsrc(ssrc_str, &ssrc)) {
+      return BadParse("Missing or invalid ssrc.", error);
+    }
+
+    streams->push_back(StreamParams::CreateLegacy(ssrc));
+  }
+  return true;
+}
+
+bool ParseSsrcs(const buzz::XmlElement* parent_elem,
+                std::vector<uint32>* ssrcs,
+                ParseError* error) {
+  for (const buzz::XmlElement* ssrc_elem =
+           parent_elem->FirstNamed(QN_JINGLE_DRAFT_SSRC);
+       ssrc_elem != NULL;
+       ssrc_elem = ssrc_elem->NextNamed(QN_JINGLE_DRAFT_SSRC)) {
+    uint32 ssrc;
+    if (!ParseSsrc(ssrc_elem->BodyText(), &ssrc)) {
+      return BadParse("Missing or invalid ssrc.", error);
+    }
+
+    ssrcs->push_back(ssrc);
+  }
+  return true;
+}
+
+bool ParseSsrcGroups(const buzz::XmlElement* parent_elem,
+                     std::vector<SsrcGroup>* ssrc_groups,
+                     ParseError* error) {
+  for (const buzz::XmlElement* group_elem =
+           parent_elem->FirstNamed(QN_JINGLE_DRAFT_SSRC_GROUP);
+       group_elem != NULL;
+       group_elem = group_elem->NextNamed(QN_JINGLE_DRAFT_SSRC_GROUP)) {
+    std::string semantics = group_elem->Attr(QN_SEMANTICS);
+    std::vector<uint32> ssrcs;
+    if (!ParseSsrcs(group_elem, &ssrcs, error)) {
+      return false;
+    }
+    ssrc_groups->push_back(SsrcGroup(semantics, ssrcs));
+  }
+  return true;
+}
+
+bool ParseJingleStream(const buzz::XmlElement* stream_elem,
+                       std::vector<StreamParams>* streams,
+                       ParseError* error) {
+  StreamParams stream;
+  // We treat the nick as a stream groupid.
+  stream.groupid = stream_elem->Attr(QN_NICK);
+  stream.id = stream_elem->Attr(QN_NAME);
+  stream.type = stream_elem->Attr(QN_TYPE);
+  stream.display = stream_elem->Attr(QN_DISPLAY);
+  stream.cname = stream_elem->Attr(QN_CNAME);
+  if (!ParseSsrcs(stream_elem, &(stream.ssrcs), error)) {
+    return false;
+  }
+  std::vector<SsrcGroup> ssrc_groups;
+  if (!ParseSsrcGroups(stream_elem, &(stream.ssrc_groups), error)) {
+    return false;
+  }
+  streams->push_back(stream);
+  return true;
+}
+
+bool ParseJingleRtpHeaderExtensions(const buzz::XmlElement* parent_elem,
+                                    std::vector<RtpHeaderExtension>* hdrexts,
+                                    ParseError* error) {
+  for (const buzz::XmlElement* hdrext_elem =
+           parent_elem->FirstNamed(QN_JINGLE_RTP_HDREXT);
+       hdrext_elem != NULL;
+       hdrext_elem = hdrext_elem->NextNamed(QN_JINGLE_RTP_HDREXT)) {
+    std::string uri = hdrext_elem->Attr(QN_URI);
+    int id = GetXmlAttr(hdrext_elem, QN_ID, 0);
+    if (id <= 0) {
+      return BadParse("Invalid RTP header extension id.", error);
+    }
+    hdrexts->push_back(RtpHeaderExtension(uri, id));
+  }
+  return true;
+}
+
+bool HasJingleStreams(const buzz::XmlElement* desc_elem) {
+  const buzz::XmlElement* streams_elem =
+      desc_elem->FirstNamed(QN_JINGLE_DRAFT_STREAMS);
+  return (streams_elem != NULL);
+}
+
+bool ParseJingleStreams(const buzz::XmlElement* desc_elem,
+                        std::vector<StreamParams>* streams,
+                        ParseError* error) {
+  const buzz::XmlElement* streams_elem =
+      desc_elem->FirstNamed(QN_JINGLE_DRAFT_STREAMS);
+  if (streams_elem == NULL) {
+    return BadParse("Missing streams element.", error);
+  }
+  for (const buzz::XmlElement* stream_elem =
+           streams_elem->FirstNamed(QN_JINGLE_DRAFT_STREAM);
+       stream_elem != NULL;
+       stream_elem = stream_elem->NextNamed(QN_JINGLE_DRAFT_STREAM)) {
+    if (!ParseJingleStream(stream_elem, streams, error)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+void WriteSsrcs(const std::vector<uint32>& ssrcs,
+                buzz::XmlElement* parent_elem) {
+  for (std::vector<uint32>::const_iterator ssrc = ssrcs.begin();
+       ssrc != ssrcs.end(); ++ssrc) {
+    buzz::XmlElement* ssrc_elem =
+        new buzz::XmlElement(QN_JINGLE_DRAFT_SSRC, false);
+    SetXmlBody(ssrc_elem, *ssrc);
+
+    parent_elem->AddElement(ssrc_elem);
+  }
+}
+
+void WriteSsrcGroups(const std::vector<SsrcGroup>& groups,
+                     buzz::XmlElement* parent_elem) {
+  for (std::vector<SsrcGroup>::const_iterator group = groups.begin();
+       group != groups.end(); ++group) {
+    buzz::XmlElement* group_elem =
+        new buzz::XmlElement(QN_JINGLE_DRAFT_SSRC_GROUP, false);
+    AddXmlAttrIfNonEmpty(group_elem, QN_SEMANTICS, group->semantics);
+    WriteSsrcs(group->ssrcs, group_elem);
+
+    parent_elem->AddElement(group_elem);
+  }
+}
+
+void WriteJingleStream(const StreamParams& stream,
+                       buzz::XmlElement* parent_elem) {
+  buzz::XmlElement* stream_elem =
+      new buzz::XmlElement(QN_JINGLE_DRAFT_STREAM, false);
+  // We treat the nick as a stream groupid.
+  AddXmlAttrIfNonEmpty(stream_elem, QN_NICK, stream.groupid);
+  AddXmlAttrIfNonEmpty(stream_elem, QN_NAME, stream.id);
+  AddXmlAttrIfNonEmpty(stream_elem, QN_TYPE, stream.type);
+  AddXmlAttrIfNonEmpty(stream_elem, QN_DISPLAY, stream.display);
+  AddXmlAttrIfNonEmpty(stream_elem, QN_CNAME, stream.cname);
+  WriteSsrcs(stream.ssrcs, stream_elem);
+  WriteSsrcGroups(stream.ssrc_groups, stream_elem);
+
+  parent_elem->AddElement(stream_elem);
+}
+
+void WriteJingleStreams(const std::vector<StreamParams>& streams,
+                        buzz::XmlElement* parent_elem) {
+  buzz::XmlElement* streams_elem =
+      new buzz::XmlElement(QN_JINGLE_DRAFT_STREAMS, true);
+  for (std::vector<StreamParams>::const_iterator stream = streams.begin();
+       stream != streams.end(); ++stream) {
+    WriteJingleStream(*stream, streams_elem);
+  }
+
+  parent_elem->AddElement(streams_elem);
+}
+
+void WriteJingleRtpHeaderExtensions(
+    const std::vector<RtpHeaderExtension>& hdrexts,
+    buzz::XmlElement* parent_elem) {
+  for (std::vector<RtpHeaderExtension>::const_iterator hdrext = hdrexts.begin();
+       hdrext != hdrexts.end(); ++hdrext) {
+    buzz::XmlElement* hdrext_elem =
+      new buzz::XmlElement(QN_JINGLE_RTP_HDREXT, false);
+    AddXmlAttr(hdrext_elem, QN_URI, hdrext->uri);
+    AddXmlAttr(hdrext_elem, QN_ID, hdrext->id);
+    parent_elem->AddElement(hdrext_elem);
+  }
+}
+
+
+}  // namespace cricket
diff --git a/libjingle/media/mediamessages.h b/libjingle/media/mediamessages.h
new file mode 100644
index 0000000..895eac9
--- /dev/null
+++ b/libjingle/media/mediamessages.h
@@ -0,0 +1,169 @@
+/*
+ * libjingle
+ * Copyright 2010 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.
+ */
+
+/*
+ * A collection of functions and types for serializing and
+ * deserializing Jingle session messages related to media.
+ * Specificially, the <notify> and <view> messages.  They are not yet
+ * standardized, but their current documentation can be found at:
+ * goto/jinglemuc
+ */
+
+#ifndef TALK_SESSION_MEDIA_MEDIAMESSAGES_H_
+#define TALK_SESSION_MEDIA_MEDIAMESSAGES_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/media/base/mediachannel.h"  // For RtpHeaderExtension
+#include "talk/media/base/streamparams.h"
+#include "webrtc/libjingle/session/parsing.h"
+#include "webrtc/p2p/base/sessiondescription.h"
+#include "webrtc/base/basictypes.h"
+
+namespace cricket {
+
+// A collection of audio and video and data streams. Most of the
+// methods are merely for convenience. Many of these methods are keyed
+// by ssrc, which is the source identifier in the RTP spec
+// (http://tools.ietf.org/html/rfc3550).
+struct MediaStreams {
+ public:
+  MediaStreams() {}
+  void CopyFrom(const MediaStreams& sources);
+
+  bool empty() const {
+    return audio_.empty() && video_.empty() && data_.empty();
+  }
+
+  std::vector<StreamParams>* mutable_audio() { return &audio_; }
+  std::vector<StreamParams>* mutable_video() { return &video_; }
+  std::vector<StreamParams>* mutable_data() { return &data_; }
+  const std::vector<StreamParams>& audio() const { return audio_; }
+  const std::vector<StreamParams>& video() const { return video_; }
+  const std::vector<StreamParams>& data() const { return data_; }
+
+  // Gets a stream, returning true if found.
+  bool GetAudioStream(
+      const StreamSelector& selector, StreamParams* stream);
+  bool GetVideoStream(
+      const StreamSelector& selector, StreamParams* stream);
+  bool GetDataStream(
+      const StreamSelector& selector, StreamParams* stream);
+  // Adds a stream.
+  void AddAudioStream(const StreamParams& stream);
+  void AddVideoStream(const StreamParams& stream);
+  void AddDataStream(const StreamParams& stream);
+  // Removes a stream, returning true if found and removed.
+  bool RemoveAudioStream(const StreamSelector& selector);
+  bool RemoveVideoStream(const StreamSelector& selector);
+  bool RemoveDataStream(const StreamSelector& selector);
+
+ private:
+  std::vector<StreamParams> audio_;
+  std::vector<StreamParams> video_;
+  std::vector<StreamParams> data_;
+
+  DISALLOW_COPY_AND_ASSIGN(MediaStreams);
+};
+
+// In a <view> message, there are a number of views specified.  This
+// represents one such view.  We currently only support "static"
+// views.
+struct StaticVideoView {
+  StaticVideoView(const StreamSelector& selector,
+                  int width, int height, int framerate)
+      : selector(selector),
+        width(width),
+        height(height),
+        framerate(framerate),
+        preference(0) {
+  }
+
+  StreamSelector selector;
+  int width;
+  int height;
+  int framerate;
+  int preference;
+};
+
+typedef std::vector<StaticVideoView> StaticVideoViews;
+
+// Represents a whole view request message, which contains many views.
+struct ViewRequest {
+  StaticVideoViews static_video_views;
+};
+
+// If the parent element (usually <jingle>) is a jingle view.
+bool IsJingleViewRequest(const buzz::XmlElement* action_elem);
+
+// Parses a view request from the parent element (usually
+// <jingle>). If it fails, it returns false and fills an error
+// message.
+bool ParseJingleViewRequest(const buzz::XmlElement* action_elem,
+                            ViewRequest* view_request,
+                            ParseError* error);
+
+// Serializes a view request to XML.  If it fails, returns false and
+// fills in an error message.
+bool WriteJingleViewRequest(const std::string& content_name,
+                            const ViewRequest& view,
+                            XmlElements* elems,
+                            WriteError* error);
+
+// TODO(pthatcher): Get rid of legacy source notify and replace with
+// description-info as soon as reflector is capable of sending it.
+bool IsSourcesNotify(const buzz::XmlElement* action_elem);
+
+// If the given elem has <streams>.
+bool HasJingleStreams(const buzz::XmlElement* desc_elem);
+
+// Parses streams from a jingle <description>.  If it fails, returns
+// false and fills an error message.
+bool ParseJingleStreams(const buzz::XmlElement* desc_elem,
+                        std::vector<StreamParams>* streams,
+                        ParseError* error);
+
+// Write a <streams> element to the parent_elem.
+void WriteJingleStreams(const std::vector<StreamParams>& streams,
+                        buzz::XmlElement* parent_elem);
+
+// Parses rtp header extensions from a jingle <description>.  If it
+// fails, returns false and fills an error message.
+bool ParseJingleRtpHeaderExtensions(
+    const buzz::XmlElement* desc_elem,
+    std::vector<RtpHeaderExtension>* hdrexts,
+    ParseError* error);
+
+// Writes <rtp-hdrext> elements to the parent_elem.
+void WriteJingleRtpHeaderExtensions(
+    const std::vector<RtpHeaderExtension>& hdrexts,
+    buzz::XmlElement* parent_elem);
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_MEDIA_MEDIAMESSAGES_H_
diff --git a/libjingle/media/mediamessages_unittest.cc b/libjingle/media/mediamessages_unittest.cc
new file mode 100644
index 0000000..b075cc7
--- /dev/null
+++ b/libjingle/media/mediamessages_unittest.cc
@@ -0,0 +1,363 @@
+/*
+ * 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 "webrtc/libjingle/media/mediamessages.h"
+
+#include <string>
+#include <vector>
+
+#include "webrtc/p2p/base/constants.h"
+#include "webrtc/libjingle/media/mediasessionclient.h"
+#include "webrtc/libjingle/xmllite/xmlelement.h"
+#include "webrtc/base/gunit.h"
+#include "webrtc/base/scoped_ptr.h"
+
+// Unit tests for mediamessages.cc.
+
+namespace cricket {
+
+namespace {
+
+static const char kViewVideoNoneXml[] =
+    "<view xmlns='google:jingle'"
+    "  name='video1'"
+    "  type='none'"
+    "/>";
+
+class MediaMessagesTest : public testing::Test {
+ public:
+  // CreateMediaSessionDescription uses a static variable cricket::NS_JINGLE_RTP
+  // defined in another file and cannot be used to initialize another static
+  // variable (http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.14)
+  MediaMessagesTest()
+      : remote_description_(CreateMediaSessionDescription("audio1", "video1")) {
+  }
+
+ protected:
+  static std::string ViewVideoStaticVgaXml(const std::string& ssrc) {
+      return "<view xmlns='google:jingle'"
+             "  name='video1'"
+             "  type='static'"
+             "  ssrc='" + ssrc + "'"
+             ">"
+             "<params"
+             "    width='640'"
+             "    height='480'"
+             "    framerate='30'"
+             "    preference='0'"
+             "  />"
+             "</view>";
+  }
+
+  static cricket::StreamParams CreateStream(const std::string& nick,
+                                            const std::string& name,
+                                            uint32 ssrc1,
+                                            uint32 ssrc2,
+                                            const std::string& semantics,
+                                            const std::string& type,
+                                            const std::string& display) {
+    StreamParams stream;
+    stream.groupid = nick;
+    stream.id = name;
+    stream.ssrcs.push_back(ssrc1);
+    stream.ssrcs.push_back(ssrc2);
+    stream.ssrc_groups.push_back(
+        cricket::SsrcGroup(semantics, stream.ssrcs));
+    stream.type = type;
+    stream.display = display;
+    return stream;
+  }
+
+  static std::string StreamsXml(const std::string& stream1,
+                                const std::string& stream2) {
+    return "<streams xmlns='google:jingle'>"
+           + stream1
+           + stream2 +
+           "</streams>";
+  }
+
+
+  static std::string StreamXml(const std::string& nick,
+                               const std::string& name,
+                               const std::string& ssrc1,
+                               const std::string& ssrc2,
+                               const std::string& semantics,
+                               const std::string& type,
+                               const std::string& display) {
+    return "<stream"
+           " nick='" + nick + "'"
+           " name='" + name + "'"
+           " type='" + type + "'"
+           " display='" + display + "'"
+           ">"
+           "<ssrc>" + ssrc1 + "</ssrc>"
+           "<ssrc>" + ssrc2 + "</ssrc>"
+           "<ssrc-group"
+           "  semantics='" + semantics + "'"
+           ">"
+           "<ssrc>" + ssrc1 + "</ssrc>"
+           "<ssrc>" + ssrc2 + "</ssrc>"
+           "</ssrc-group>"
+           "</stream>";
+  }
+
+  static std::string HeaderExtensionsXml(const std::string& hdrext1,
+                                         const std::string& hdrext2) {
+    return "<rtp:description xmlns:rtp=\"urn:xmpp:jingle:apps:rtp:1\">"
+           + hdrext1
+           + hdrext2 +
+           "</rtp:description>";
+  }
+
+  static std::string HeaderExtensionXml(const std::string& uri,
+                                        const std::string& id) {
+    return "<rtp:rtp-hdrext"
+           " uri='" + uri + "'"
+           " id='" + id + "'"
+           "/>";
+  }
+
+  static cricket::SessionDescription* CreateMediaSessionDescription(
+      const std::string& audio_content_name,
+      const std::string& video_content_name) {
+    cricket::SessionDescription* desc = new cricket::SessionDescription();
+    desc->AddContent(audio_content_name, cricket::NS_JINGLE_RTP,
+                     new cricket::AudioContentDescription());
+    desc->AddContent(video_content_name, cricket::NS_JINGLE_RTP,
+                     new cricket::VideoContentDescription());
+    return desc;
+  }
+
+  size_t ClearXmlElements(cricket::XmlElements* elements) {
+    size_t size = elements->size();
+    for (size_t i = 0; i < size; i++) {
+      delete elements->at(i);
+    }
+    elements->clear();
+    return size;
+  }
+
+  rtc::scoped_ptr<cricket::SessionDescription> remote_description_;
+};
+
+}  // anonymous namespace
+
+// Test serializing/deserializing an empty <view> message.
+TEST_F(MediaMessagesTest, ViewNoneToFromXml) {
+  buzz::XmlElement* expected_view_elem =
+      buzz::XmlElement::ForStr(kViewVideoNoneXml);
+  rtc::scoped_ptr<buzz::XmlElement> action_elem(
+      new buzz::XmlElement(QN_JINGLE));
+
+  EXPECT_FALSE(cricket::IsJingleViewRequest(action_elem.get()));
+  action_elem->AddElement(expected_view_elem);
+  EXPECT_TRUE(cricket::IsJingleViewRequest(action_elem.get()));
+
+  cricket::ViewRequest view_request;
+  cricket::XmlElements actual_view_elems;
+  cricket::WriteError error;
+
+  ASSERT_TRUE(cricket::WriteJingleViewRequest(
+      "video1", view_request, &actual_view_elems, &error));
+
+  ASSERT_EQ(1U, actual_view_elems.size());
+  EXPECT_EQ(expected_view_elem->Str(), actual_view_elems[0]->Str());
+  ClearXmlElements(&actual_view_elems);
+
+  cricket::ParseError parse_error;
+  EXPECT_TRUE(cricket::IsJingleViewRequest(action_elem.get()));
+  ASSERT_TRUE(cricket::ParseJingleViewRequest(
+      action_elem.get(), &view_request, &parse_error));
+  EXPECT_EQ(0U, view_request.static_video_views.size());
+}
+
+// Test serializing/deserializing an a simple vga <view> message.
+TEST_F(MediaMessagesTest, ViewVgaToFromXml) {
+  rtc::scoped_ptr<buzz::XmlElement> action_elem(
+      new buzz::XmlElement(QN_JINGLE));
+  buzz::XmlElement* expected_view_elem1 =
+      buzz::XmlElement::ForStr(ViewVideoStaticVgaXml("1234"));
+  buzz::XmlElement* expected_view_elem2 =
+      buzz::XmlElement::ForStr(ViewVideoStaticVgaXml("2468"));
+  action_elem->AddElement(expected_view_elem1);
+  action_elem->AddElement(expected_view_elem2);
+
+  cricket::ViewRequest view_request;
+  cricket::XmlElements actual_view_elems;
+  cricket::WriteError error;
+
+  view_request.static_video_views.push_back(cricket::StaticVideoView(
+      cricket::StreamSelector(1234), 640, 480, 30));
+  view_request.static_video_views.push_back(cricket::StaticVideoView(
+      cricket::StreamSelector(2468), 640, 480, 30));
+
+  ASSERT_TRUE(cricket::WriteJingleViewRequest(
+      "video1", view_request, &actual_view_elems, &error));
+
+  ASSERT_EQ(2U, actual_view_elems.size());
+  EXPECT_EQ(expected_view_elem1->Str(), actual_view_elems[0]->Str());
+  EXPECT_EQ(expected_view_elem2->Str(), actual_view_elems[1]->Str());
+  ClearXmlElements(&actual_view_elems);
+
+  view_request.static_video_views.clear();
+  cricket::ParseError parse_error;
+  EXPECT_TRUE(cricket::IsJingleViewRequest(action_elem.get()));
+  ASSERT_TRUE(cricket::ParseJingleViewRequest(
+      action_elem.get(), &view_request, &parse_error));
+  EXPECT_EQ(2U, view_request.static_video_views.size());
+  EXPECT_EQ(1234U, view_request.static_video_views[0].selector.ssrc);
+  EXPECT_EQ(640, view_request.static_video_views[0].width);
+  EXPECT_EQ(480, view_request.static_video_views[0].height);
+  EXPECT_EQ(30, view_request.static_video_views[0].framerate);
+  EXPECT_EQ(2468U, view_request.static_video_views[1].selector.ssrc);
+}
+
+// Test deserializing bad view XML.
+TEST_F(MediaMessagesTest, ParseBadViewXml) {
+  rtc::scoped_ptr<buzz::XmlElement> action_elem(
+      new buzz::XmlElement(QN_JINGLE));
+  buzz::XmlElement* view_elem =
+      buzz::XmlElement::ForStr(ViewVideoStaticVgaXml("not-an-ssrc"));
+  action_elem->AddElement(view_elem);
+
+  cricket::ViewRequest view_request;
+  cricket::ParseError parse_error;
+  ASSERT_FALSE(cricket::ParseJingleViewRequest(
+      action_elem.get(), &view_request, &parse_error));
+}
+
+
+// Test serializing/deserializing typical streams xml.
+TEST_F(MediaMessagesTest, StreamsToFromXml) {
+  rtc::scoped_ptr<buzz::XmlElement> expected_streams_elem(
+      buzz::XmlElement::ForStr(
+          StreamsXml(
+              StreamXml("nick1", "stream1", "101", "102",
+                        "semantics1", "type1", "display1"),
+              StreamXml("nick2", "stream2", "201", "202",
+                        "semantics2", "type2", "display2"))));
+
+  std::vector<cricket::StreamParams> expected_streams;
+  expected_streams.push_back(CreateStream("nick1", "stream1", 101U, 102U,
+                                          "semantics1", "type1", "display1"));
+  expected_streams.push_back(CreateStream("nick2", "stream2", 201U, 202U,
+                                          "semantics2", "type2", "display2"));
+
+  rtc::scoped_ptr<buzz::XmlElement> actual_desc_elem(
+      new buzz::XmlElement(QN_JINGLE_RTP_CONTENT));
+  cricket::WriteJingleStreams(expected_streams, actual_desc_elem.get());
+
+  const buzz::XmlElement* actual_streams_elem =
+      actual_desc_elem->FirstNamed(QN_JINGLE_DRAFT_STREAMS);
+  ASSERT_TRUE(actual_streams_elem != NULL);
+  EXPECT_EQ(expected_streams_elem->Str(), actual_streams_elem->Str());
+
+  rtc::scoped_ptr<buzz::XmlElement> expected_desc_elem(
+      new buzz::XmlElement(QN_JINGLE_RTP_CONTENT));
+  expected_desc_elem->AddElement(new buzz::XmlElement(
+      *expected_streams_elem));
+  std::vector<cricket::StreamParams> actual_streams;
+  cricket::ParseError parse_error;
+
+  EXPECT_TRUE(cricket::HasJingleStreams(expected_desc_elem.get()));
+  ASSERT_TRUE(cricket::ParseJingleStreams(
+      expected_desc_elem.get(), &actual_streams, &parse_error));
+  EXPECT_EQ(2U, actual_streams.size());
+  EXPECT_EQ(expected_streams[0], actual_streams[0]);
+  EXPECT_EQ(expected_streams[1], actual_streams[1]);
+}
+
+// Test deserializing bad streams xml.
+TEST_F(MediaMessagesTest, StreamsFromBadXml) {
+  rtc::scoped_ptr<buzz::XmlElement> streams_elem(
+      buzz::XmlElement::ForStr(
+          StreamsXml(
+              StreamXml("nick1", "name1", "101", "not-an-ssrc",
+                        "semantics1", "type1", "display1"),
+              StreamXml("nick2", "name2", "202", "not-an-ssrc",
+                        "semantics2", "type2", "display2"))));
+  rtc::scoped_ptr<buzz::XmlElement> desc_elem(
+      new buzz::XmlElement(QN_JINGLE_RTP_CONTENT));
+  desc_elem->AddElement(new buzz::XmlElement(*streams_elem));
+
+  std::vector<cricket::StreamParams> actual_streams;
+  cricket::ParseError parse_error;
+  ASSERT_FALSE(cricket::ParseJingleStreams(
+      desc_elem.get(), &actual_streams, &parse_error));
+}
+
+// Test serializing/deserializing typical RTP Header Extension xml.
+TEST_F(MediaMessagesTest, HeaderExtensionsToFromXml) {
+  rtc::scoped_ptr<buzz::XmlElement> expected_desc_elem(
+      buzz::XmlElement::ForStr(
+          HeaderExtensionsXml(
+              HeaderExtensionXml("abc", "123"),
+              HeaderExtensionXml("def", "456"))));
+
+  std::vector<cricket::RtpHeaderExtension> expected_hdrexts;
+  expected_hdrexts.push_back(RtpHeaderExtension("abc", 123));
+  expected_hdrexts.push_back(RtpHeaderExtension("def", 456));
+
+  rtc::scoped_ptr<buzz::XmlElement> actual_desc_elem(
+      new buzz::XmlElement(QN_JINGLE_RTP_CONTENT));
+  cricket::WriteJingleRtpHeaderExtensions(expected_hdrexts, actual_desc_elem.get());
+
+  ASSERT_TRUE(actual_desc_elem != NULL);
+  EXPECT_EQ(expected_desc_elem->Str(), actual_desc_elem->Str());
+
+  std::vector<cricket::RtpHeaderExtension> actual_hdrexts;
+  cricket::ParseError parse_error;
+  ASSERT_TRUE(cricket::ParseJingleRtpHeaderExtensions(
+      expected_desc_elem.get(), &actual_hdrexts, &parse_error));
+  EXPECT_EQ(2U, actual_hdrexts.size());
+  EXPECT_EQ(expected_hdrexts[0], actual_hdrexts[0]);
+  EXPECT_EQ(expected_hdrexts[1], actual_hdrexts[1]);
+}
+
+// Test deserializing bad RTP header extension xml.
+TEST_F(MediaMessagesTest, HeaderExtensionsFromBadXml) {
+  std::vector<cricket::RtpHeaderExtension> actual_hdrexts;
+  cricket::ParseError parse_error;
+
+  rtc::scoped_ptr<buzz::XmlElement> desc_elem(
+      buzz::XmlElement::ForStr(
+          HeaderExtensionsXml(
+              HeaderExtensionXml("abc", "123"),
+              HeaderExtensionXml("def", "not-an-id"))));
+  ASSERT_FALSE(cricket::ParseJingleRtpHeaderExtensions(
+      desc_elem.get(), &actual_hdrexts, &parse_error));
+
+  desc_elem.reset(
+      buzz::XmlElement::ForStr(
+          HeaderExtensionsXml(
+              HeaderExtensionXml("abc", "123"),
+              HeaderExtensionXml("def", "-1"))));
+  ASSERT_FALSE(cricket::ParseJingleRtpHeaderExtensions(
+      desc_elem.get(), &actual_hdrexts, &parse_error));
+}
+
+}  // namespace cricket
diff --git a/libjingle/media/mediasessionclient.cc b/libjingle/media/mediasessionclient.cc
new file mode 100644
index 0000000..efa8cf3
--- /dev/null
+++ b/libjingle/media/mediasessionclient.cc
@@ -0,0 +1,1161 @@
+/*
+ * 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 "webrtc/libjingle/media/mediasessionclient.h"
+
+#include "talk/media/base/capturemanager.h"
+#include "talk/media/base/cryptoparams.h"
+#include "talk/media/sctp/sctpdataengine.h"
+#include "webrtc/p2p/base/constants.h"
+#include "webrtc/libjingle/session/parsing.h"
+#include "webrtc/libjingle/media/mediamessages.h"
+#include "talk/session/media/srtpfilter.h"
+#include "webrtc/libjingle/xmllite/qname.h"
+#include "webrtc/libjingle/xmllite/xmlconstants.h"
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/base/helpers.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/stringencode.h"
+#include "webrtc/base/stringutils.h"
+
+namespace cricket {
+
+#if !defined(DISABLE_MEDIA_ENGINE_FACTORY)
+MediaSessionClient::MediaSessionClient(
+    const buzz::Jid& jid, SessionManager *manager)
+    : jid_(jid),
+      session_manager_(manager),
+      focus_call_(NULL),
+      channel_manager_(new ChannelManager(session_manager_->worker_thread())),
+      desc_factory_(channel_manager_,
+          session_manager_->transport_desc_factory()),
+      multisession_enabled_(false) {
+  Construct();
+}
+#endif
+
+MediaSessionClient::MediaSessionClient(
+    const buzz::Jid& jid, SessionManager *manager,
+    MediaEngineInterface* media_engine,
+    DataEngineInterface* data_media_engine,
+    DeviceManagerInterface* device_manager)
+    : jid_(jid),
+      session_manager_(manager),
+      focus_call_(NULL),
+      channel_manager_(new ChannelManager(
+          media_engine, data_media_engine,
+          device_manager, new CaptureManager(),
+          session_manager_->worker_thread())),
+      desc_factory_(channel_manager_,
+                    session_manager_->transport_desc_factory()),
+      multisession_enabled_(false) {
+  Construct();
+}
+
+void MediaSessionClient::Construct() {
+  // Register ourselves as the handler of audio and video sessions.
+  session_manager_->AddClient(NS_JINGLE_RTP, this);
+  // Forward device notifications.
+  SignalDevicesChange.repeat(channel_manager_->SignalDevicesChange);
+  // Bring up the channel manager.
+  // In previous versions of ChannelManager, this was done automatically
+  // in the constructor.
+  channel_manager_->Init();
+}
+
+MediaSessionClient::~MediaSessionClient() {
+  // Destroy all calls
+  std::map<uint32, Call *>::iterator it;
+  while (calls_.begin() != calls_.end()) {
+    std::map<uint32, Call *>::iterator it = calls_.begin();
+    DestroyCall((*it).second);
+  }
+
+  // Delete channel manager. This will wait for the channels to exit
+  delete channel_manager_;
+
+  // Remove ourselves from the client map.
+  session_manager_->RemoveClient(NS_JINGLE_RTP);
+}
+
+Call *MediaSessionClient::CreateCall() {
+  Call *call = new Call(this);
+  calls_[call->id()] = call;
+  SignalCallCreate(call);
+  return call;
+}
+
+void MediaSessionClient::OnSessionCreate(Session *session,
+                                         bool received_initiate) {
+  if (received_initiate) {
+    session->SignalState.connect(this, &MediaSessionClient::OnSessionState);
+  }
+}
+
+void MediaSessionClient::OnSessionState(BaseSession* base_session,
+                                        BaseSession::State state) {
+  // MediaSessionClient can only be used with a Session*, so it's
+  // safe to cast here.
+  Session* session = static_cast<Session*>(base_session);
+
+  if (state == Session::STATE_RECEIVEDINITIATE) {
+    // The creation of the call must happen after the session has
+    // processed the initiate message because we need the
+    // remote_description to know what content names to use in the
+    // call.
+
+    // If our accept would have no codecs, then we must reject this call.
+    const SessionDescription* offer = session->remote_description();
+    const SessionDescription* accept = CreateAnswer(offer, CallOptions());
+    const ContentInfo* audio_content = GetFirstAudioContent(accept);
+    bool audio_rejected = (!audio_content) ? true : audio_content->rejected;
+    const AudioContentDescription* audio_desc = (!audio_content) ? NULL :
+        static_cast<const AudioContentDescription*>(audio_content->description);
+
+    // For some reason, we need a call even if we reject. So, either find a
+    // matching call or create a new one.
+    // The matching of existing calls is used to support the multi-session mode
+    // required for p2p handoffs: ie. once a MUC call is established, a new
+    // session may be established for the same call but is direct between the
+    // clients. To indicate that this is the case, the initiator of the incoming
+    // session is set to be the same as the remote name of the MUC for the
+    // existing session, thus the client can know that this is a new session for
+    // the existing call, rather than a whole new call.
+    Call* call = NULL;
+    if (multisession_enabled_) {
+      call = FindCallByRemoteName(session->initiator_name());
+    }
+
+    if (call == NULL) {
+      // Could not find a matching call, so create a new one.
+      call = CreateCall();
+    }
+
+    session_map_[session->id()] = call;
+    call->IncomingSession(session, offer);
+
+    if (audio_rejected || !audio_desc || audio_desc->codecs().size() == 0) {
+      session->Reject(STR_TERMINATE_INCOMPATIBLE_PARAMETERS);
+    }
+    delete accept;
+  }
+}
+
+void MediaSessionClient::DestroyCall(Call *call) {
+  // Change focus away, signal destruction
+
+  if (call == focus_call_)
+    SetFocus(NULL);
+  SignalCallDestroy(call);
+
+  // Remove it from calls_ map and delete
+
+  std::map<uint32, Call *>::iterator it = calls_.find(call->id());
+  if (it != calls_.end())
+    calls_.erase(it);
+
+  delete call;
+}
+
+void MediaSessionClient::OnSessionDestroy(Session *session) {
+  // Find the call this session is in, remove it
+  SessionMap::iterator it = session_map_.find(session->id());
+  ASSERT(it != session_map_.end());
+  if (it != session_map_.end()) {
+    Call *call = (*it).second;
+    session_map_.erase(it);
+    call->RemoveSession(session);
+  }
+}
+
+Call *MediaSessionClient::GetFocus() {
+  return focus_call_;
+}
+
+void MediaSessionClient::SetFocus(Call *call) {
+  Call *old_focus_call = focus_call_;
+  if (focus_call_ != call) {
+    if (focus_call_ != NULL)
+      focus_call_->EnableChannels(false);
+    focus_call_ = call;
+    if (focus_call_ != NULL)
+      focus_call_->EnableChannels(true);
+    SignalFocus(focus_call_, old_focus_call);
+  }
+}
+
+void MediaSessionClient::JoinCalls(Call *call_to_join, Call *call) {
+  // Move all sessions from call to call_to_join, delete call.
+  // If call_to_join has focus, added sessions should have enabled channels.
+
+  if (focus_call_ == call)
+    SetFocus(NULL);
+  call_to_join->Join(call, focus_call_ == call_to_join);
+  DestroyCall(call);
+}
+
+Session *MediaSessionClient::CreateSession(Call *call) {
+  std::string id;
+  return CreateSession(id, call);
+}
+
+Session *MediaSessionClient::CreateSession(const std::string& id, Call* call) {
+  const std::string& type = NS_JINGLE_RTP;
+  Session *session = session_manager_->CreateSession(id, jid().Str(), type);
+  session_map_[session->id()] = call;
+  return session;
+}
+
+Call *MediaSessionClient::FindCallByRemoteName(const std::string &remote_name) {
+  SessionMap::const_iterator call;
+  for (call = session_map_.begin(); call != session_map_.end(); ++call) {
+    std::vector<Session *> sessions = call->second->sessions();
+    std::vector<Session *>::const_iterator session;
+    for (session = sessions.begin(); session != sessions.end(); ++session) {
+      if (remote_name == (*session)->remote_name()) {
+        return call->second;
+      }
+    }
+  }
+
+  return NULL;
+}
+
+// TODO(pthatcher): Move all of the parsing and writing functions into
+// mediamessages.cc, with unit tests.
+bool ParseGingleAudioCodec(const buzz::XmlElement* element, AudioCodec* out) {
+  int id = GetXmlAttr(element, QN_ID, -1);
+  if (id < 0)
+    return false;
+
+  std::string name = GetXmlAttr(element, QN_NAME, buzz::STR_EMPTY);
+  int clockrate = GetXmlAttr(element, QN_CLOCKRATE, 0);
+  int bitrate = GetXmlAttr(element, QN_BITRATE, 0);
+  int channels = GetXmlAttr(element, QN_CHANNELS, 1);
+  *out = AudioCodec(id, name, clockrate, bitrate, channels, 0);
+  return true;
+}
+
+bool ParseGingleVideoCodec(const buzz::XmlElement* element, VideoCodec* out) {
+  int id = GetXmlAttr(element, QN_ID, -1);
+  if (id < 0)
+    return false;
+
+  std::string name = GetXmlAttr(element, QN_NAME, buzz::STR_EMPTY);
+  int width = GetXmlAttr(element, QN_WIDTH, 0);
+  int height = GetXmlAttr(element, QN_HEIGHT, 0);
+  int framerate = GetXmlAttr(element, QN_FRAMERATE, 0);
+
+  *out = VideoCodec(id, name, width, height, framerate, 0);
+  return true;
+}
+
+// Parses an ssrc string as a legacy stream.  If it fails, returns
+// false and fills an error message.
+bool ParseSsrcAsLegacyStream(const std::string& ssrc_str,
+                             std::vector<StreamParams>* streams,
+                             ParseError* error) {
+  if (!ssrc_str.empty()) {
+    uint32 ssrc;
+    if (!rtc::FromString(ssrc_str, &ssrc)) {
+      return BadParse("Missing or invalid ssrc.", error);
+    }
+
+    streams->push_back(StreamParams::CreateLegacy(ssrc));
+  }
+  return true;
+}
+
+void ParseGingleSsrc(const buzz::XmlElement* parent_elem,
+                     const buzz::QName& name,
+                     MediaContentDescription* media) {
+  const buzz::XmlElement* ssrc_elem = parent_elem->FirstNamed(name);
+  if (ssrc_elem) {
+    ParseError error;
+    ParseSsrcAsLegacyStream(
+        ssrc_elem->BodyText(), &(media->mutable_streams()), &error);
+  }
+}
+
+bool ParseCryptoParams(const buzz::XmlElement* element,
+                       CryptoParams* out,
+                       ParseError* error) {
+  if (!element->HasAttr(QN_CRYPTO_SUITE)) {
+    return BadParse("crypto: crypto-suite attribute missing ", error);
+  } else if (!element->HasAttr(QN_CRYPTO_KEY_PARAMS)) {
+    return BadParse("crypto: key-params attribute missing ", error);
+  } else if (!element->HasAttr(QN_CRYPTO_TAG)) {
+    return BadParse("crypto: tag attribute missing ", error);
+  }
+
+  const std::string& crypto_suite = element->Attr(QN_CRYPTO_SUITE);
+  const std::string& key_params = element->Attr(QN_CRYPTO_KEY_PARAMS);
+  const int tag = GetXmlAttr(element, QN_CRYPTO_TAG, 0);
+  const std::string& session_params =
+      element->Attr(QN_CRYPTO_SESSION_PARAMS);  // Optional.
+
+  *out = CryptoParams(tag, crypto_suite, key_params, session_params);
+  return true;
+}
+
+
+// Parse the first encryption element found with a matching 'usage'
+// element.
+// <usage/> is specific to Gingle. In Jingle, <crypto/> is already
+// scoped to a content.
+// Return false if there was an encryption element and it could not be
+// parsed.
+bool ParseGingleEncryption(const buzz::XmlElement* desc,
+                           const buzz::QName& usage,
+                           MediaContentDescription* media,
+                           ParseError* error) {
+  for (const buzz::XmlElement* encryption = desc->FirstNamed(QN_ENCRYPTION);
+       encryption != NULL;
+       encryption = encryption->NextNamed(QN_ENCRYPTION)) {
+    if (encryption->FirstNamed(usage) != NULL) {
+      if (GetXmlAttr(encryption, QN_ENCRYPTION_REQUIRED, false)) {
+        media->set_crypto_required(CT_SDES);
+      }
+      for (const buzz::XmlElement* crypto = encryption->FirstNamed(QN_CRYPTO);
+           crypto != NULL;
+           crypto = crypto->NextNamed(QN_CRYPTO)) {
+        CryptoParams params;
+        if (!ParseCryptoParams(crypto, &params, error)) {
+          return false;
+        }
+        media->AddCrypto(params);
+      }
+      break;
+    }
+  }
+  return true;
+}
+
+void ParseBandwidth(const buzz::XmlElement* parent_elem,
+                    MediaContentDescription* media) {
+  const buzz::XmlElement* bw_elem = GetXmlChild(parent_elem, LN_BANDWIDTH);
+  int bandwidth_kbps = -1;
+  if (bw_elem && rtc::FromString(bw_elem->BodyText(), &bandwidth_kbps)) {
+    if (bandwidth_kbps >= 0) {
+      media->set_bandwidth(bandwidth_kbps * 1000);
+    }
+  }
+}
+
+bool ParseGingleAudioContent(const buzz::XmlElement* content_elem,
+                             ContentDescription** content,
+                             ParseError* error) {
+  AudioContentDescription* audio = new AudioContentDescription();
+
+  int preference = kMaxPayloadId;
+  if (content_elem->FirstElement()) {
+    for (const buzz::XmlElement* codec_elem =
+             content_elem->FirstNamed(QN_GINGLE_AUDIO_PAYLOADTYPE);
+         codec_elem != NULL;
+         codec_elem = codec_elem->NextNamed(QN_GINGLE_AUDIO_PAYLOADTYPE)) {
+      AudioCodec codec;
+      if (ParseGingleAudioCodec(codec_elem, &codec)) {
+        codec.preference = preference--;
+        audio->AddCodec(codec);
+      }
+    }
+  } else {
+    // For backward compatibility, we can assume the other client is
+    // an old version of Talk if it has no audio payload types at all.
+    audio->AddCodec(AudioCodec(103, "ISAC", 16000, -1, 1, 1));
+    audio->AddCodec(AudioCodec(0, "PCMU", 8000, 64000, 1, 0));
+  }
+
+  ParseGingleSsrc(content_elem, QN_GINGLE_AUDIO_SRCID, audio);
+
+  if (!ParseGingleEncryption(content_elem, QN_GINGLE_AUDIO_CRYPTO_USAGE,
+                             audio, error)) {
+    return false;
+  }
+
+  *content = audio;
+  return true;
+}
+
+bool ParseGingleVideoContent(const buzz::XmlElement* content_elem,
+                             ContentDescription** content,
+                             ParseError* error) {
+  VideoContentDescription* video = new VideoContentDescription();
+
+  int preference = kMaxPayloadId;
+  for (const buzz::XmlElement* codec_elem =
+           content_elem->FirstNamed(QN_GINGLE_VIDEO_PAYLOADTYPE);
+       codec_elem != NULL;
+       codec_elem = codec_elem->NextNamed(QN_GINGLE_VIDEO_PAYLOADTYPE)) {
+    VideoCodec codec;
+    if (ParseGingleVideoCodec(codec_elem, &codec)) {
+      codec.preference = preference--;
+      video->AddCodec(codec);
+    }
+  }
+
+  ParseGingleSsrc(content_elem, QN_GINGLE_VIDEO_SRCID, video);
+  ParseBandwidth(content_elem, video);
+
+  if (!ParseGingleEncryption(content_elem, QN_GINGLE_VIDEO_CRYPTO_USAGE,
+                             video, error)) {
+    return false;
+  }
+
+  *content = video;
+  return true;
+}
+
+void ParsePayloadTypeParameters(const buzz::XmlElement* element,
+                                std::map<std::string, std::string>* paramap) {
+  for (const buzz::XmlElement* param = element->FirstNamed(QN_PARAMETER);
+       param != NULL; param = param->NextNamed(QN_PARAMETER)) {
+    std::string name  = GetXmlAttr(param, QN_PAYLOADTYPE_PARAMETER_NAME,
+                                   buzz::STR_EMPTY);
+    std::string value = GetXmlAttr(param, QN_PAYLOADTYPE_PARAMETER_VALUE,
+                                   buzz::STR_EMPTY);
+    if (!name.empty() && !value.empty()) {
+      paramap->insert(make_pair(name, value));
+    }
+  }
+}
+
+void ParseFeedbackParams(const buzz::XmlElement* element,
+                         FeedbackParams* params) {
+  for (const buzz::XmlElement* param = element->FirstNamed(QN_JINGLE_RTCP_FB);
+       param != NULL; param = param->NextNamed(QN_JINGLE_RTCP_FB)) {
+    std::string type = GetXmlAttr(param, QN_TYPE, buzz::STR_EMPTY);
+    std::string subtype = GetXmlAttr(param, QN_SUBTYPE, buzz::STR_EMPTY);
+    if (!type.empty()) {
+      params->Add(FeedbackParam(type, subtype));
+    }
+  }
+}
+
+void AddFeedbackParams(const FeedbackParams& additional_params,
+                       FeedbackParams* params) {
+  for (size_t i = 0; i < additional_params.params().size(); ++i) {
+    params->Add(additional_params.params()[i]);
+  }
+}
+
+int FindWithDefault(const std::map<std::string, std::string>& map,
+                    const std::string& key, const int def) {
+  std::map<std::string, std::string>::const_iterator iter = map.find(key);
+  return (iter == map.end()) ? def : atoi(iter->second.c_str());
+}
+
+
+// Parse the first encryption element found.
+// Return false if there was an encryption element and it could not be
+// parsed.
+bool ParseJingleEncryption(const buzz::XmlElement* content_elem,
+                           MediaContentDescription* media,
+                           ParseError* error) {
+  const buzz::XmlElement* encryption =
+          content_elem->FirstNamed(QN_ENCRYPTION);
+  if (encryption == NULL) {
+      return true;
+  }
+
+  if (GetXmlAttr(encryption, QN_ENCRYPTION_REQUIRED, false)) {
+    media->set_crypto_required(CT_SDES);
+  }
+
+  for (const buzz::XmlElement* crypto = encryption->FirstNamed(QN_CRYPTO);
+       crypto != NULL;
+       crypto = crypto->NextNamed(QN_CRYPTO)) {
+    CryptoParams params;
+    if (!ParseCryptoParams(crypto, &params, error)) {
+      return false;
+    }
+    media->AddCrypto(params);
+  }
+  return true;
+}
+
+bool ParseJingleAudioCodec(const buzz::XmlElement* elem, AudioCodec* codec) {
+  int id = GetXmlAttr(elem, QN_ID, -1);
+  if (id < 0)
+    return false;
+
+  std::string name = GetXmlAttr(elem, QN_NAME, buzz::STR_EMPTY);
+  int clockrate = GetXmlAttr(elem, QN_CLOCKRATE, 0);
+  int channels = GetXmlAttr(elem, QN_CHANNELS, 1);
+
+  std::map<std::string, std::string> paramap;
+  ParsePayloadTypeParameters(elem, &paramap);
+  int bitrate = FindWithDefault(paramap, PAYLOADTYPE_PARAMETER_BITRATE, 0);
+
+  *codec = AudioCodec(id, name, clockrate, bitrate, channels, 0);
+  ParseFeedbackParams(elem, &codec->feedback_params);
+  return true;
+}
+
+bool ParseJingleVideoCodec(const buzz::XmlElement* elem, VideoCodec* codec) {
+  int id = GetXmlAttr(elem, QN_ID, -1);
+  if (id < 0)
+    return false;
+
+  std::string name = GetXmlAttr(elem, QN_NAME, buzz::STR_EMPTY);
+
+  std::map<std::string, std::string> paramap;
+  ParsePayloadTypeParameters(elem, &paramap);
+  int width = FindWithDefault(paramap, PAYLOADTYPE_PARAMETER_WIDTH, 0);
+  int height = FindWithDefault(paramap, PAYLOADTYPE_PARAMETER_HEIGHT, 0);
+  int framerate = FindWithDefault(paramap, PAYLOADTYPE_PARAMETER_FRAMERATE, 0);
+
+  *codec = VideoCodec(id, name, width, height, framerate, 0);
+  codec->params = paramap;
+  ParseFeedbackParams(elem, &codec->feedback_params);
+  return true;
+}
+
+bool ParseJingleDataCodec(const buzz::XmlElement* elem, DataCodec* codec) {
+  int id = GetXmlAttr(elem, QN_ID, -1);
+  if (id < 0)
+    return false;
+
+  std::string name = GetXmlAttr(elem, QN_NAME, buzz::STR_EMPTY);
+
+  *codec = DataCodec(id, name, 0);
+  ParseFeedbackParams(elem, &codec->feedback_params);
+  return true;
+}
+
+bool ParseJingleStreamsOrLegacySsrc(const buzz::XmlElement* desc_elem,
+                                    MediaContentDescription* media,
+                                    ParseError* error) {
+  if (HasJingleStreams(desc_elem)) {
+    if (!ParseJingleStreams(desc_elem, &(media->mutable_streams()), error)) {
+      return false;
+    }
+  } else {
+    const std::string ssrc_str = desc_elem->Attr(QN_SSRC);
+    if (!ParseSsrcAsLegacyStream(
+            ssrc_str, &(media->mutable_streams()), error)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool ParseJingleAudioContent(const buzz::XmlElement* content_elem,
+                             ContentDescription** content,
+                             ParseError* error) {
+  rtc::scoped_ptr<AudioContentDescription> audio(
+      new AudioContentDescription());
+
+  FeedbackParams content_feedback_params;
+  ParseFeedbackParams(content_elem, &content_feedback_params);
+
+  int preference = kMaxPayloadId;
+  for (const buzz::XmlElement* payload_elem =
+           content_elem->FirstNamed(QN_JINGLE_RTP_PAYLOADTYPE);
+      payload_elem != NULL;
+      payload_elem = payload_elem->NextNamed(QN_JINGLE_RTP_PAYLOADTYPE)) {
+    AudioCodec codec;
+    if (ParseJingleAudioCodec(payload_elem, &codec)) {
+      AddFeedbackParams(content_feedback_params, &codec.feedback_params);
+      codec.preference = preference--;
+      audio->AddCodec(codec);
+    }
+  }
+
+  if (!ParseJingleStreamsOrLegacySsrc(content_elem, audio.get(), error)) {
+    return false;
+  }
+
+  if (!ParseJingleEncryption(content_elem, audio.get(), error)) {
+    return false;
+  }
+
+  audio->set_rtcp_mux(content_elem->FirstNamed(QN_JINGLE_RTCP_MUX) != NULL);
+
+  RtpHeaderExtensions hdrexts;
+  if (!ParseJingleRtpHeaderExtensions(content_elem, &hdrexts, error)) {
+    return false;
+  }
+  audio->set_rtp_header_extensions(hdrexts);
+
+  *content = audio.release();
+  return true;
+}
+
+bool ParseJingleVideoContent(const buzz::XmlElement* content_elem,
+                             ContentDescription** content,
+                             ParseError* error) {
+  rtc::scoped_ptr<VideoContentDescription> video(
+      new VideoContentDescription());
+
+  FeedbackParams content_feedback_params;
+  ParseFeedbackParams(content_elem, &content_feedback_params);
+
+  int preference = kMaxPayloadId;
+  for (const buzz::XmlElement* payload_elem =
+           content_elem->FirstNamed(QN_JINGLE_RTP_PAYLOADTYPE);
+      payload_elem != NULL;
+      payload_elem = payload_elem->NextNamed(QN_JINGLE_RTP_PAYLOADTYPE)) {
+    VideoCodec codec;
+    if (ParseJingleVideoCodec(payload_elem, &codec)) {
+      AddFeedbackParams(content_feedback_params, &codec.feedback_params);
+      codec.preference = preference--;
+      video->AddCodec(codec);
+    }
+  }
+
+  if (!ParseJingleStreamsOrLegacySsrc(content_elem, video.get(), error)) {
+    return false;
+  }
+  ParseBandwidth(content_elem, video.get());
+
+  if (!ParseJingleEncryption(content_elem, video.get(), error)) {
+    return false;
+  }
+
+  video->set_rtcp_mux(content_elem->FirstNamed(QN_JINGLE_RTCP_MUX) != NULL);
+
+  RtpHeaderExtensions hdrexts;
+  if (!ParseJingleRtpHeaderExtensions(content_elem, &hdrexts, error)) {
+    return false;
+  }
+  video->set_rtp_header_extensions(hdrexts);
+
+  *content = video.release();
+  return true;
+}
+
+bool ParseJingleSctpDataContent(const buzz::XmlElement* content_elem,
+                                ContentDescription** content,
+                                ParseError* error) {
+  rtc::scoped_ptr<DataContentDescription> data(
+      new DataContentDescription());
+  data->set_protocol(kMediaProtocolSctp);
+
+  for (const buzz::XmlElement* stream_elem =
+           content_elem->FirstNamed(QN_JINGLE_DRAFT_SCTP_STREAM);
+       stream_elem != NULL;
+       stream_elem = stream_elem->NextNamed(QN_JINGLE_DRAFT_SCTP_STREAM)) {
+    StreamParams stream;
+    stream.groupid = stream_elem->Attr(QN_NICK);
+    stream.id = stream_elem->Attr(QN_NAME);
+    uint32 sid;
+    if (!rtc::FromString(stream_elem->Attr(QN_SID), &sid)) {
+      return BadParse("Missing or invalid sid.", error);
+    }
+    if (sid > kMaxSctpSid) {
+      return BadParse("SID is greater than max value.", error);
+    }
+
+    stream.ssrcs.push_back(sid);
+    data->mutable_streams().push_back(stream);
+  }
+
+  *content = data.release();
+  return true;
+}
+
+bool ParseJingleRtpDataContent(const buzz::XmlElement* content_elem,
+                               ContentDescription** content,
+                               ParseError* error) {
+  DataContentDescription* data = new DataContentDescription();
+
+  FeedbackParams content_feedback_params;
+  ParseFeedbackParams(content_elem, &content_feedback_params);
+
+  int preference = kMaxPayloadId;
+  for (const buzz::XmlElement* payload_elem =
+           content_elem->FirstNamed(QN_JINGLE_RTP_PAYLOADTYPE);
+      payload_elem != NULL;
+      payload_elem = payload_elem->NextNamed(QN_JINGLE_RTP_PAYLOADTYPE)) {
+    DataCodec codec;
+    if (ParseJingleDataCodec(payload_elem, &codec)) {
+      AddFeedbackParams(content_feedback_params, &codec.feedback_params);
+      codec.preference = preference--;
+      data->AddCodec(codec);
+    }
+  }
+
+  if (!ParseJingleStreamsOrLegacySsrc(content_elem, data, error)) {
+    return false;
+  }
+  ParseBandwidth(content_elem, data);
+
+  if (!ParseJingleEncryption(content_elem, data, error)) {
+    return false;
+  }
+
+  data->set_rtcp_mux(content_elem->FirstNamed(QN_JINGLE_RTCP_MUX) != NULL);
+
+  *content = data;
+  return true;
+}
+
+bool MediaSessionClient::ParseContent(SignalingProtocol protocol,
+                                      const buzz::XmlElement* content_elem,
+                                      ContentDescription** content,
+                                      ParseError* error) {
+  if (protocol == PROTOCOL_GINGLE) {
+    const std::string& content_type = content_elem->Name().Namespace();
+    if (NS_GINGLE_AUDIO == content_type) {
+      return ParseGingleAudioContent(content_elem, content, error);
+    } else if (NS_GINGLE_VIDEO == content_type) {
+      return ParseGingleVideoContent(content_elem, content, error);
+    } else {
+      return BadParse("Unknown content type: " + content_type, error);
+    }
+  } else {
+    const std::string& content_type = content_elem->Name().Namespace();
+    // We use the XMLNS of the <description> element to determine if
+    // it's RTP or SCTP.
+    if (content_type == NS_JINGLE_DRAFT_SCTP) {
+      return ParseJingleSctpDataContent(content_elem, content, error);
+    }
+
+    std::string media;
+    if (!RequireXmlAttr(content_elem, QN_JINGLE_CONTENT_MEDIA, &media, error))
+      return false;
+
+    if (media == JINGLE_CONTENT_MEDIA_AUDIO) {
+      return ParseJingleAudioContent(content_elem, content, error);
+    } else if (media == JINGLE_CONTENT_MEDIA_VIDEO) {
+      return ParseJingleVideoContent(content_elem, content, error);
+    } else if (media == JINGLE_CONTENT_MEDIA_DATA) {
+      return ParseJingleRtpDataContent(content_elem, content, error);
+    } else {
+      return BadParse("Unknown media: " + media, error);
+    }
+  }
+}
+
+buzz::XmlElement* CreateGingleAudioCodecElem(const AudioCodec& codec) {
+  buzz::XmlElement* payload_type =
+      new buzz::XmlElement(QN_GINGLE_AUDIO_PAYLOADTYPE, true);
+  AddXmlAttr(payload_type, QN_ID, codec.id);
+  payload_type->AddAttr(QN_NAME, codec.name);
+  if (codec.clockrate > 0)
+    AddXmlAttr(payload_type, QN_CLOCKRATE, codec.clockrate);
+  if (codec.bitrate > 0)
+    AddXmlAttr(payload_type, QN_BITRATE, codec.bitrate);
+  if (codec.channels > 1)
+    AddXmlAttr(payload_type, QN_CHANNELS, codec.channels);
+  return payload_type;
+}
+
+buzz::XmlElement* CreateGingleVideoCodecElem(const VideoCodec& codec) {
+  buzz::XmlElement* payload_type =
+      new buzz::XmlElement(QN_GINGLE_VIDEO_PAYLOADTYPE, true);
+  AddXmlAttr(payload_type, QN_ID, codec.id);
+  payload_type->AddAttr(QN_NAME, codec.name);
+  AddXmlAttr(payload_type, QN_WIDTH, codec.width);
+  AddXmlAttr(payload_type, QN_HEIGHT, codec.height);
+  AddXmlAttr(payload_type, QN_FRAMERATE, codec.framerate);
+  return payload_type;
+}
+
+buzz::XmlElement* CreateGingleSsrcElem(const buzz::QName& name, uint32 ssrc) {
+  buzz::XmlElement* elem = new buzz::XmlElement(name, true);
+  if (ssrc) {
+    SetXmlBody(elem, ssrc);
+  }
+  return elem;
+}
+
+buzz::XmlElement* CreateBandwidthElem(const buzz::QName& name, int bps) {
+  int kbps = bps / 1000;
+  buzz::XmlElement* elem = new buzz::XmlElement(name);
+  elem->AddAttr(buzz::QN_TYPE, "AS");
+  SetXmlBody(elem, kbps);
+  return elem;
+}
+
+// For Jingle, usage_qname is empty.
+buzz::XmlElement* CreateJingleEncryptionElem(const CryptoParamsVec& cryptos,
+                                             bool required) {
+  buzz::XmlElement* encryption_elem = new buzz::XmlElement(QN_ENCRYPTION);
+
+  if (required) {
+    encryption_elem->SetAttr(QN_ENCRYPTION_REQUIRED, "true");
+  }
+
+  for (CryptoParamsVec::const_iterator i = cryptos.begin();
+       i != cryptos.end();
+       ++i) {
+    buzz::XmlElement* crypto_elem = new buzz::XmlElement(QN_CRYPTO);
+
+    AddXmlAttr(crypto_elem, QN_CRYPTO_TAG, i->tag);
+    crypto_elem->AddAttr(QN_CRYPTO_SUITE, i->cipher_suite);
+    crypto_elem->AddAttr(QN_CRYPTO_KEY_PARAMS, i->key_params);
+    if (!i->session_params.empty()) {
+      crypto_elem->AddAttr(QN_CRYPTO_SESSION_PARAMS, i->session_params);
+    }
+    encryption_elem->AddElement(crypto_elem);
+  }
+  return encryption_elem;
+}
+
+buzz::XmlElement* CreateGingleEncryptionElem(const CryptoParamsVec& cryptos,
+                                             const buzz::QName& usage_qname,
+                                             bool required) {
+  buzz::XmlElement* encryption_elem =
+      CreateJingleEncryptionElem(cryptos, required);
+
+  if (required) {
+    encryption_elem->SetAttr(QN_ENCRYPTION_REQUIRED, "true");
+  }
+
+  buzz::XmlElement* usage_elem = new buzz::XmlElement(usage_qname);
+  encryption_elem->AddElement(usage_elem);
+
+  return encryption_elem;
+}
+
+buzz::XmlElement* CreateGingleAudioContentElem(
+    const AudioContentDescription* audio,
+    bool crypto_required) {
+  buzz::XmlElement* elem =
+      new buzz::XmlElement(QN_GINGLE_AUDIO_CONTENT, true);
+
+  for (AudioCodecs::const_iterator codec = audio->codecs().begin();
+       codec != audio->codecs().end(); ++codec) {
+    elem->AddElement(CreateGingleAudioCodecElem(*codec));
+  }
+  if (audio->has_ssrcs()) {
+    elem->AddElement(CreateGingleSsrcElem(
+        QN_GINGLE_AUDIO_SRCID, audio->first_ssrc()));
+  }
+
+  const CryptoParamsVec& cryptos = audio->cryptos();
+  if (!cryptos.empty()) {
+    elem->AddElement(CreateGingleEncryptionElem(cryptos,
+                                                QN_GINGLE_AUDIO_CRYPTO_USAGE,
+                                                crypto_required));
+  }
+  return elem;
+}
+
+buzz::XmlElement* CreateGingleVideoContentElem(
+    const VideoContentDescription* video,
+    bool crypto_required) {
+  buzz::XmlElement* elem =
+      new buzz::XmlElement(QN_GINGLE_VIDEO_CONTENT, true);
+
+  for (VideoCodecs::const_iterator codec = video->codecs().begin();
+       codec != video->codecs().end(); ++codec) {
+    elem->AddElement(CreateGingleVideoCodecElem(*codec));
+  }
+  if (video->has_ssrcs()) {
+    elem->AddElement(CreateGingleSsrcElem(
+        QN_GINGLE_VIDEO_SRCID, video->first_ssrc()));
+  }
+  if (video->bandwidth() != kAutoBandwidth) {
+    elem->AddElement(CreateBandwidthElem(QN_GINGLE_VIDEO_BANDWIDTH,
+                                         video->bandwidth()));
+  }
+
+  const CryptoParamsVec& cryptos = video->cryptos();
+  if (!cryptos.empty()) {
+    elem->AddElement(CreateGingleEncryptionElem(cryptos,
+                                                QN_GINGLE_VIDEO_CRYPTO_USAGE,
+                                                crypto_required));
+  }
+
+  return elem;
+}
+
+template <class T>
+buzz::XmlElement* CreatePayloadTypeParameterElem(
+    const std::string& name, T value) {
+  buzz::XmlElement* elem = new buzz::XmlElement(QN_PARAMETER);
+
+  elem->AddAttr(QN_PAYLOADTYPE_PARAMETER_NAME, name);
+  AddXmlAttr(elem, QN_PAYLOADTYPE_PARAMETER_VALUE, value);
+
+  return elem;
+}
+
+void AddRtcpFeedbackElem(buzz::XmlElement* elem,
+                      const FeedbackParams& feedback_params) {
+  std::vector<FeedbackParam>::const_iterator it;
+  for (it = feedback_params.params().begin();
+       it != feedback_params.params().end(); ++it) {
+    buzz::XmlElement* fb_elem = new buzz::XmlElement(QN_JINGLE_RTCP_FB);
+    fb_elem->AddAttr(QN_TYPE, it->id());
+    fb_elem->AddAttr(QN_SUBTYPE, it->param());
+    elem->AddElement(fb_elem);
+  }
+}
+
+buzz::XmlElement* CreateJingleAudioCodecElem(const AudioCodec& codec) {
+  buzz::XmlElement* elem = new buzz::XmlElement(QN_JINGLE_RTP_PAYLOADTYPE);
+
+  AddXmlAttr(elem, QN_ID, codec.id);
+  elem->AddAttr(QN_NAME, codec.name);
+  if (codec.clockrate > 0) {
+    AddXmlAttr(elem, QN_CLOCKRATE, codec.clockrate);
+  }
+  if (codec.bitrate > 0) {
+    elem->AddElement(CreatePayloadTypeParameterElem(
+        PAYLOADTYPE_PARAMETER_BITRATE, codec.bitrate));
+  }
+  if (codec.channels > 1) {
+    AddXmlAttr(elem, QN_CHANNELS, codec.channels);
+  }
+
+  AddRtcpFeedbackElem(elem, codec.feedback_params);
+
+  return elem;
+}
+
+buzz::XmlElement* CreateJingleVideoCodecElem(const VideoCodec& codec) {
+  buzz::XmlElement* elem = new buzz::XmlElement(QN_JINGLE_RTP_PAYLOADTYPE);
+
+  AddXmlAttr(elem, QN_ID, codec.id);
+  elem->AddAttr(QN_NAME, codec.name);
+  elem->AddElement(CreatePayloadTypeParameterElem(
+      PAYLOADTYPE_PARAMETER_WIDTH, codec.width));
+  elem->AddElement(CreatePayloadTypeParameterElem(
+      PAYLOADTYPE_PARAMETER_HEIGHT, codec.height));
+  elem->AddElement(CreatePayloadTypeParameterElem(
+      PAYLOADTYPE_PARAMETER_FRAMERATE, codec.framerate));
+
+  AddRtcpFeedbackElem(elem, codec.feedback_params);
+
+  CodecParameterMap::const_iterator param_iter;
+  for (param_iter = codec.params.begin(); param_iter != codec.params.end();
+       ++param_iter) {
+    elem->AddElement(CreatePayloadTypeParameterElem(param_iter->first,
+                                                    param_iter->second));
+  }
+
+  return elem;
+}
+
+buzz::XmlElement* CreateJingleDataCodecElem(const DataCodec& codec) {
+  buzz::XmlElement* elem = new buzz::XmlElement(QN_JINGLE_RTP_PAYLOADTYPE);
+
+  AddXmlAttr(elem, QN_ID, codec.id);
+  elem->AddAttr(QN_NAME, codec.name);
+
+  AddRtcpFeedbackElem(elem, codec.feedback_params);
+
+  return elem;
+}
+
+void WriteLegacyJingleSsrc(const MediaContentDescription* media,
+                           buzz::XmlElement* elem) {
+  if (media->has_ssrcs()) {
+    AddXmlAttr(elem, QN_SSRC, media->first_ssrc());
+  }
+}
+
+void WriteJingleStreamsOrLegacySsrc(const MediaContentDescription* media,
+                                    buzz::XmlElement* desc_elem) {
+  if (!media->multistream()) {
+    WriteLegacyJingleSsrc(media, desc_elem);
+  } else {
+    WriteJingleStreams(media->streams(), desc_elem);
+  }
+}
+
+buzz::XmlElement* CreateJingleAudioContentElem(
+    const AudioContentDescription* audio, bool crypto_required) {
+  buzz::XmlElement* elem =
+      new buzz::XmlElement(QN_JINGLE_RTP_CONTENT, true);
+
+  elem->SetAttr(QN_JINGLE_CONTENT_MEDIA, JINGLE_CONTENT_MEDIA_AUDIO);
+  WriteJingleStreamsOrLegacySsrc(audio, elem);
+
+  for (AudioCodecs::const_iterator codec = audio->codecs().begin();
+       codec != audio->codecs().end(); ++codec) {
+    elem->AddElement(CreateJingleAudioCodecElem(*codec));
+  }
+
+  const CryptoParamsVec& cryptos = audio->cryptos();
+  if (!cryptos.empty()) {
+    elem->AddElement(CreateJingleEncryptionElem(cryptos, crypto_required));
+  }
+
+  if (audio->rtcp_mux()) {
+    elem->AddElement(new buzz::XmlElement(QN_JINGLE_RTCP_MUX));
+  }
+
+  WriteJingleRtpHeaderExtensions(audio->rtp_header_extensions(), elem);
+
+  return elem;
+}
+
+buzz::XmlElement* CreateJingleVideoContentElem(
+    const VideoContentDescription* video, bool crypto_required) {
+  buzz::XmlElement* elem =
+      new buzz::XmlElement(QN_JINGLE_RTP_CONTENT, true);
+
+  elem->SetAttr(QN_JINGLE_CONTENT_MEDIA, JINGLE_CONTENT_MEDIA_VIDEO);
+  WriteJingleStreamsOrLegacySsrc(video, elem);
+
+  for (VideoCodecs::const_iterator codec = video->codecs().begin();
+       codec != video->codecs().end(); ++codec) {
+    elem->AddElement(CreateJingleVideoCodecElem(*codec));
+  }
+
+  const CryptoParamsVec& cryptos = video->cryptos();
+  if (!cryptos.empty()) {
+    elem->AddElement(CreateJingleEncryptionElem(cryptos, crypto_required));
+  }
+
+  if (video->rtcp_mux()) {
+    elem->AddElement(new buzz::XmlElement(QN_JINGLE_RTCP_MUX));
+  }
+
+  if (video->bandwidth() != kAutoBandwidth) {
+    elem->AddElement(CreateBandwidthElem(QN_JINGLE_RTP_BANDWIDTH,
+                                         video->bandwidth()));
+  }
+
+  WriteJingleRtpHeaderExtensions(video->rtp_header_extensions(), elem);
+
+  return elem;
+}
+
+buzz::XmlElement* CreateJingleSctpDataContentElem(
+    const DataContentDescription* data) {
+  buzz::XmlElement* content_elem =
+      new buzz::XmlElement(QN_JINGLE_DRAFT_SCTP_CONTENT, true);
+  for (std::vector<StreamParams>::const_iterator
+           stream = data->streams().begin();
+       stream != data->streams().end(); ++stream) {
+    buzz::XmlElement* stream_elem =
+      new buzz::XmlElement(QN_JINGLE_DRAFT_SCTP_STREAM, false);
+    AddXmlAttrIfNonEmpty(stream_elem, QN_NICK, stream->groupid);
+    AddXmlAttrIfNonEmpty(stream_elem, QN_NAME, stream->id);
+    if (!stream->ssrcs.empty()) {
+      AddXmlAttr(stream_elem, QN_SID, stream->ssrcs[0]);
+    }
+    content_elem->AddElement(stream_elem);
+  }
+  return content_elem;;
+}
+
+buzz::XmlElement* CreateJingleRtpDataContentElem(
+    const DataContentDescription* data, bool crypto_required) {
+
+  buzz::XmlElement* elem =
+      new buzz::XmlElement(QN_JINGLE_RTP_CONTENT, true);
+
+  elem->SetAttr(QN_JINGLE_CONTENT_MEDIA, JINGLE_CONTENT_MEDIA_DATA);
+  WriteJingleStreamsOrLegacySsrc(data, elem);
+
+  for (DataCodecs::const_iterator codec = data->codecs().begin();
+       codec != data->codecs().end(); ++codec) {
+    elem->AddElement(CreateJingleDataCodecElem(*codec));
+  }
+
+  const CryptoParamsVec& cryptos = data->cryptos();
+  if (!cryptos.empty()) {
+    elem->AddElement(CreateJingleEncryptionElem(cryptos, crypto_required));
+  }
+
+  if (data->rtcp_mux()) {
+    elem->AddElement(new buzz::XmlElement(QN_JINGLE_RTCP_MUX));
+  }
+
+  if (data->bandwidth() != kAutoBandwidth) {
+    elem->AddElement(CreateBandwidthElem(QN_JINGLE_RTP_BANDWIDTH,
+                                         data->bandwidth()));
+  }
+
+  return elem;
+}
+
+bool IsSctp(const DataContentDescription* data) {
+  return (data->protocol() == kMediaProtocolSctp ||
+    data->protocol() == kMediaProtocolDtlsSctp);
+}
+
+buzz::XmlElement* CreateJingleDataContentElem(
+    const DataContentDescription* data, bool crypto_required) {
+  if (IsSctp(data)) {
+    return CreateJingleSctpDataContentElem(data);
+  } else {
+    return CreateJingleRtpDataContentElem(data, crypto_required);
+  }
+}
+
+bool MediaSessionClient::IsWritable(SignalingProtocol protocol,
+                                    const ContentDescription* content) {
+  const MediaContentDescription* media =
+      static_cast<const MediaContentDescription*>(content);
+  if (protocol == PROTOCOL_GINGLE &&
+      media->type() == MEDIA_TYPE_DATA) {
+    return false;
+  }
+  return true;
+}
+
+bool MediaSessionClient::WriteContent(SignalingProtocol protocol,
+                                      const ContentDescription* content,
+                                      buzz::XmlElement** elem,
+                                      WriteError* error) {
+  const MediaContentDescription* media =
+      static_cast<const MediaContentDescription*>(content);
+  bool crypto_required = secure() == SEC_REQUIRED;
+
+  if (media->type() == MEDIA_TYPE_AUDIO) {
+    const AudioContentDescription* audio =
+        static_cast<const AudioContentDescription*>(media);
+    if (protocol == PROTOCOL_GINGLE) {
+      *elem = CreateGingleAudioContentElem(audio, crypto_required);
+    } else {
+      *elem = CreateJingleAudioContentElem(audio, crypto_required);
+    }
+  } else if (media->type() == MEDIA_TYPE_VIDEO) {
+    const VideoContentDescription* video =
+        static_cast<const VideoContentDescription*>(media);
+    if (protocol == PROTOCOL_GINGLE) {
+      *elem = CreateGingleVideoContentElem(video, crypto_required);
+    } else {
+      *elem = CreateJingleVideoContentElem(video, crypto_required);
+    }
+  } else if (media->type() == MEDIA_TYPE_DATA) {
+    const DataContentDescription* data =
+        static_cast<const DataContentDescription*>(media);
+    if (protocol == PROTOCOL_GINGLE) {
+      return BadWrite("Data channel not supported with Gingle.", error);
+    } else {
+      *elem = CreateJingleDataContentElem(data, crypto_required);
+    }
+  } else {
+    return BadWrite("Unknown content type: " +
+                    rtc::ToString<int>(media->type()), error);
+  }
+
+  return true;
+}
+
+}  // namespace cricket
diff --git a/libjingle/media/mediasessionclient.h b/libjingle/media/mediasessionclient.h
new file mode 100644
index 0000000..23956e8
--- /dev/null
+++ b/libjingle/media/mediasessionclient.h
@@ -0,0 +1,175 @@
+/*
+ * 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.
+ */
+
+#ifndef TALK_SESSION_MEDIA_MEDIASESSIONCLIENT_H_
+#define TALK_SESSION_MEDIA_MEDIASESSIONCLIENT_H_
+
+#include <algorithm>
+#include <map>
+#include <string>
+#include <vector>
+#include "talk/media/base/cryptoparams.h"
+#include "webrtc/p2p/base/session.h"
+#include "webrtc/libjingle/session/sessionclient.h"
+#include "webrtc/p2p/base/sessiondescription.h"
+#include "webrtc/libjingle/session/sessionmanager.h"
+#include "webrtc/libjingle/media/call.h"
+#include "talk/session/media/channelmanager.h"
+#include "talk/session/media/mediasession.h"
+#include "webrtc/base/messagequeue.h"
+#include "webrtc/base/sigslot.h"
+#include "webrtc/base/sigslotrepeater.h"
+#include "webrtc/base/thread.h"
+
+namespace cricket {
+
+class Call;
+
+class MediaSessionClient : public SessionClient, public sigslot::has_slots<> {
+ public:
+#if !defined(DISABLE_MEDIA_ENGINE_FACTORY)
+  MediaSessionClient(const buzz::Jid& jid, SessionManager *manager);
+#endif
+  // Alternative constructor, allowing injection of media_engine
+  // and device_manager.
+  MediaSessionClient(const buzz::Jid& jid, SessionManager *manager,
+                     MediaEngineInterface* media_engine,
+                     DataEngineInterface* data_media_engine,
+                     DeviceManagerInterface* device_manager);
+  ~MediaSessionClient();
+
+  const buzz::Jid &jid() const { return jid_; }
+  SessionManager* session_manager() const { return session_manager_; }
+  ChannelManager* channel_manager() const { return channel_manager_; }
+
+  // Return mapping of call ids to Calls.
+  const std::map<uint32, Call *>& calls() const { return calls_; }
+
+  // The settings below combine with the settings on SessionManager to choose
+
+  // whether SDES-SRTP, DTLS-SRTP, or no security should be used. The possible
+  // combinations are shown in the following table. Note that where either DTLS
+  // or SDES is possible, DTLS is preferred. Thus to require either SDES or
+  // DTLS, but not mandate DTLS, set SDES to require and DTLS to enable.
+  //
+  //              | SDES:Disable   | SDES:Enable    | SDES:Require   |
+  // ----------------------------------------------------------------|
+  // DTLS:Disable | No SRTP        | SDES Optional  | SDES Mandatory |
+  // DTLS:Enable  | DTLS Optional  | DTLS/SDES Opt  | DTLS/SDES Mand |
+  // DTLS:Require | DTLS Mandatory | DTLS Mandatory | DTLS Mandatory |
+
+  // Control use of SDES-SRTP.
+  SecurePolicy secure() const { return desc_factory_.secure(); }
+  void set_secure(SecurePolicy s) { desc_factory_.set_secure(s); }
+
+  // Control use of multiple sessions in a call.
+  void set_multisession_enabled(bool multisession_enabled) {
+    multisession_enabled_ = multisession_enabled;
+  }
+
+  int GetCapabilities() { return channel_manager_->GetCapabilities(); }
+
+  Call *CreateCall();
+  void DestroyCall(Call *call);
+
+  Call *GetFocus();
+  void SetFocus(Call *call);
+
+  void JoinCalls(Call *call_to_join, Call *call);
+
+  bool GetAudioInputDevices(std::vector<std::string>* names) {
+    return channel_manager_->GetAudioInputDevices(names);
+  }
+  bool GetAudioOutputDevices(std::vector<std::string>* names) {
+    return channel_manager_->GetAudioOutputDevices(names);
+  }
+  bool GetVideoCaptureDevices(std::vector<std::string>* names) {
+    return channel_manager_->GetVideoCaptureDevices(names);
+  }
+
+  bool SetAudioOptions(const std::string& in_name, const std::string& out_name,
+                       const AudioOptions& options) {
+    return channel_manager_->SetAudioOptions(in_name, out_name, options);
+  }
+  bool SetOutputVolume(int level) {
+    return channel_manager_->SetOutputVolume(level);
+  }
+  bool SetCaptureDevice(const std::string& cam_device) {
+    return channel_manager_->SetCaptureDevice(cam_device);
+  }
+
+  SessionDescription* CreateOffer(const CallOptions& options) {
+    return desc_factory_.CreateOffer(options, NULL);
+  }
+  SessionDescription* CreateAnswer(const SessionDescription* offer,
+                                   const CallOptions& options) {
+    return desc_factory_.CreateAnswer(offer, options, NULL);
+  }
+
+  sigslot::signal2<Call *, Call *> SignalFocus;
+  sigslot::signal1<Call *> SignalCallCreate;
+  sigslot::signal1<Call *> SignalCallDestroy;
+  sigslot::repeater0<> SignalDevicesChange;
+
+  virtual bool ParseContent(SignalingProtocol protocol,
+                            const buzz::XmlElement* elem,
+                            ContentDescription** content,
+                            ParseError* error);
+  virtual bool IsWritable(SignalingProtocol protocol,
+                          const ContentDescription* content);
+  virtual bool WriteContent(SignalingProtocol protocol,
+                            const ContentDescription* content,
+                            buzz::XmlElement** elem,
+                            WriteError* error);
+
+ private:
+  void Construct();
+  void OnSessionCreate(Session *session, bool received_initiate);
+  void OnSessionState(BaseSession *session, BaseSession::State state);
+  void OnSessionDestroy(Session *session);
+  Session *CreateSession(Call *call);
+  Session *CreateSession(const std::string& id, Call* call);
+  Call *FindCallByRemoteName(const std::string &remote_name);
+
+  buzz::Jid jid_;
+  SessionManager* session_manager_;
+  Call *focus_call_;
+  ChannelManager *channel_manager_;
+  MediaSessionDescriptionFactory desc_factory_;
+  bool multisession_enabled_;
+  std::map<uint32, Call *> calls_;
+
+  // Maintain a mapping of session id to call.
+  typedef std::map<std::string, Call *> SessionMap;
+  SessionMap session_map_;
+
+  friend class Call;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_MEDIA_MEDIASESSIONCLIENT_H_
diff --git a/libjingle/media/mediasessionclient_unittest.cc b/libjingle/media/mediasessionclient_unittest.cc
new file mode 100644
index 0000000..fb4eb0a
--- /dev/null
+++ b/libjingle/media/mediasessionclient_unittest.cc
@@ -0,0 +1,3324 @@
+/*
+ * 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 "webrtc/p2p/base/constants.h"
+#include "webrtc/p2p/client/basicportallocator.h"
+#include "webrtc/libjingle/media/mediasessionclient.h"
+#include "webrtc/libjingle/xmllite/xmlbuilder.h"
+#include "webrtc/libjingle/xmllite/xmlelement.h"
+#include "webrtc/libjingle/xmllite/xmlprinter.h"
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/base/gunit.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/base/ssladapter.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",   8000,  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",   8000,  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='8000' 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='8000'>                   " \
+     "          <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.recv_audio = true;
+  options.recv_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.recv_audio = true;
+  options.recv_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", 8000, 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(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(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);
+}
+
+class MediaSessionTest : public ::testing::Test {};
+
+TEST_F(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_F(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_F(MediaSessionTest, JingleGoodVideoInitiateWithBandwidth) {
+  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  rtc::scoped_ptr<buzz::XmlElement> elem;
+  test->ExpectVideoBandwidth(42000);
+  test->TestGoodIncomingInitiate(
+      kJingleVideoInitiateWithBandwidth, VideoCallOptions(), elem.use());
+}
+
+TEST_F(MediaSessionTest, JingleGoodVideoInitiateWithRtcpMux) {
+  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  rtc::scoped_ptr<buzz::XmlElement> elem;
+  test->ExpectVideoRtcpMux(true);
+  test->TestGoodIncomingInitiate(
+      kJingleVideoInitiateWithRtcpMux, VideoCallOptions(), elem.use());
+}
+
+TEST_F(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_F(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_F(MediaSessionTest, JingleRejectAudio) {
+  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  rtc::scoped_ptr<buzz::XmlElement> elem;
+  cricket::CallOptions options = VideoCallOptions();
+  options.recv_audio = false;
+  options.data_channel_type = cricket::DCT_RTP;
+  test->TestRejectOffer(kJingleVideoInitiateWithRtpData, options, elem.use());
+}
+
+TEST_F(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_F(MediaSessionTest, JingleRejectData) {
+  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  rtc::scoped_ptr<buzz::XmlElement> elem;
+  test->TestRejectOffer(
+      kJingleVideoInitiateWithRtpData, VideoCallOptions(), elem.use());
+}
+
+TEST_F(MediaSessionTest, JingleRejectVideoAndData) {
+  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  rtc::scoped_ptr<buzz::XmlElement> elem;
+  test->TestRejectOffer(
+      kJingleVideoInitiateWithRtpData, AudioCallOptions(), elem.use());
+}
+
+TEST_F(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_F(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_F(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_F(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_F(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_F(MediaSessionTest, JingleBadInitiateNoAudioCodecs) {
+  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  test->TestBadIncomingInitiate(kJingleInitiateNoAudioCodecs);
+}
+
+TEST_F(MediaSessionTest, JingleBadInitiateNoSupportedAudioCodecs) {
+  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  test->TestBadIncomingInitiate(kJingleInitiateNoSupportedAudioCodecs);
+}
+
+TEST_F(MediaSessionTest, JingleBadInitiateWrongClockrates) {
+  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  test->TestBadIncomingInitiate(kJingleInitiateWrongClockrates);
+}
+
+TEST_F(MediaSessionTest, JingleBadInitiateWrongChannels) {
+  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  test->TestBadIncomingInitiate(kJingleInitiateWrongChannels);
+}
+
+TEST_F(MediaSessionTest, JingleBadInitiateNoPayloadTypes) {
+  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  test->TestBadIncomingInitiate(kJingleInitiateNoPayloadTypes);
+}
+
+TEST_F(MediaSessionTest, JingleBadInitiateDynamicWithoutNames) {
+  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  test->TestBadIncomingInitiate(kJingleInitiateDynamicWithoutNames);
+}
+
+TEST_F(MediaSessionTest, JingleGoodOutgoingInitiate) {
+  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  test->TestGoodOutgoingInitiate(AudioCallOptions());
+}
+
+TEST_F(MediaSessionTest, JingleGoodOutgoingInitiateWithBandwidth) {
+  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  cricket::CallOptions options = VideoCallOptions();
+  options.video_bandwidth = 42000;
+  test->TestGoodOutgoingInitiate(options);
+}
+
+TEST_F(MediaSessionTest, JingleGoodOutgoingInitiateWithRtcpMux) {
+  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  cricket::CallOptions options = VideoCallOptions();
+  options.rtcp_mux_enabled = true;
+  test->TestGoodOutgoingInitiate(options);
+}
+
+TEST_F(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_F(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_F(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_F(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_F(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_F(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_F(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_F(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_F(MediaSessionTest, JingleInitiateWithRequiredUnsupportedCrypto) {
+  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  test->TestBadIncomingInitiate(
+      AddEncryption(kJingleInitiate, kJingleRequiredUnsupportedCryptoOffer));
+}
+
+// Offer has unsupported REQUIRED crypto and session is secure, fail.
+TEST_F(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_F(MediaSessionTest,
+       JingleInitiateWithRequiredUnsupportedCryptoWhenSecureRequired) {
+  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  test->MakeSignalingSecure(cricket::SEC_REQUIRED);
+  test->TestBadIncomingInitiate(
+      AddEncryption(kJingleInitiate, kJingleRequiredUnsupportedCryptoOffer));
+}
+
+TEST_F(MediaSessionTest, JingleGoodOutgoingInitiateWithCrypto) {
+  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  test->ExpectCrypto(cricket::SEC_ENABLED);
+  test->TestGoodOutgoingInitiate(AudioCallOptions());
+}
+
+TEST_F(MediaSessionTest, JingleGoodOutgoingInitiateWithCryptoRequired) {
+  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  test->ExpectCrypto(cricket::SEC_REQUIRED);
+  test->TestGoodOutgoingInitiate(AudioCallOptions());
+}
+
+TEST_F(MediaSessionTest, JingleIncomingAcceptWithSsrcs) {
+  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  cricket::CallOptions options = VideoCallOptions();
+  options.is_muc = true;
+  test->TestIncomingAcceptWithSsrcs(kJingleAcceptWithSsrcs, options);
+}
+
+TEST_F(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_F(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_F(MediaSessionTest, JingleStreamsUpdateAndView) {
+  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  test->TestStreamsUpdateAndViewRequests();
+}
+
+TEST_F(MediaSessionTest, JingleSendVideoStreamUpdate) {
+  rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  test->TestSendVideoStreamUpdate();
+}
+
+// Gingle tests
+
+TEST_F(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_F(MediaSessionTest, GingleGoodVideoInitiateWithBandwidth) {
+  rtc::scoped_ptr<buzz::XmlElement> elem;
+  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->ExpectVideoBandwidth(42000);
+  test->TestGoodIncomingInitiate(
+      kGingleVideoInitiateWithBandwidth, VideoCallOptions(), elem.use());
+}
+
+TEST_F(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_F(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_F(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_F(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_F(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_F(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_F(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_F(MediaSessionTest, GingleBadInitiateNoSupportedAudioCodecs) {
+  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->TestBadIncomingInitiate(kGingleInitiateNoSupportedAudioCodecs);
+}
+
+TEST_F(MediaSessionTest, GingleBadInitiateWrongClockrates) {
+  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->TestBadIncomingInitiate(kGingleInitiateWrongClockrates);
+}
+
+TEST_F(MediaSessionTest, GingleBadInitiateWrongChannels) {
+  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->TestBadIncomingInitiate(kGingleInitiateWrongChannels);
+}
+
+TEST_F(MediaSessionTest, GingleBadInitiateNoPayloadTypes) {
+  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->TestBadIncomingInitiate(kGingleInitiateNoPayloadTypes);
+}
+
+TEST_F(MediaSessionTest, GingleBadInitiateDynamicWithoutNames) {
+  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->TestBadIncomingInitiate(kGingleInitiateDynamicWithoutNames);
+}
+
+TEST_F(MediaSessionTest, GingleGoodOutgoingInitiate) {
+  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->TestGoodOutgoingInitiate(AudioCallOptions());
+}
+
+TEST_F(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_F(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_F(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_F(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_F(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_F(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_F(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_F(MediaSessionTest, GingleInitiateWithRequiredUnsupportedCrypto) {
+  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->TestBadIncomingInitiate(
+      AddEncryption(kGingleInitiate, kGingleRequiredUnsupportedCryptoOffer));
+}
+
+// Offer has unsupported REQUIRED crypto and session is secure, fail.
+TEST_F(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_F(MediaSessionTest,
+       GingleInitiateWithRequiredUnsupportedCryptoWhenSecureRequired) {
+  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->MakeSignalingSecure(cricket::SEC_REQUIRED);
+  test->TestBadIncomingInitiate(
+      AddEncryption(kGingleInitiate, kGingleRequiredUnsupportedCryptoOffer));
+}
+
+TEST_F(MediaSessionTest, GingleGoodOutgoingInitiateWithCrypto) {
+  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->ExpectCrypto(cricket::SEC_ENABLED);
+  test->TestGoodOutgoingInitiate(AudioCallOptions());
+}
+
+TEST_F(MediaSessionTest, GingleGoodOutgoingInitiateWithCryptoRequired) {
+  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->ExpectCrypto(cricket::SEC_REQUIRED);
+  test->TestGoodOutgoingInitiate(AudioCallOptions());
+}
+
+TEST_F(MediaSessionTest, GingleIncomingAcceptWithSsrcs) {
+  rtc::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  cricket::CallOptions options = VideoCallOptions();
+  options.is_muc = true;
+  test->TestIncomingAcceptWithSsrcs(kGingleAcceptWithSsrcs, options);
+}
+
+TEST_F(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);
+}
diff --git a/p2p/base/parsing.cc b/libjingle/session/parsing.cc
similarity index 98%
rename from p2p/base/parsing.cc
rename to libjingle/session/parsing.cc
index 04d7e79..9a6100a 100644
--- a/p2p/base/parsing.cc
+++ b/libjingle/session/parsing.cc
@@ -8,7 +8,7 @@
  *  be found in the AUTHORS file in the root of the source tree.
  */
 
-#include "webrtc/p2p/base/parsing.h"
+#include "webrtc/libjingle/session/parsing.h"
 
 #include <stdlib.h>
 #include <algorithm>
diff --git a/p2p/base/parsing.h b/libjingle/session/parsing.h
similarity index 100%
rename from p2p/base/parsing.h
rename to libjingle/session/parsing.h
diff --git a/p2p/base/sessionclient.h b/libjingle/session/sessionclient.h
similarity index 100%
rename from p2p/base/sessionclient.h
rename to libjingle/session/sessionclient.h
diff --git a/p2p/base/sessionmanager.cc b/libjingle/session/sessionmanager.cc
similarity index 98%
rename from p2p/base/sessionmanager.cc
rename to libjingle/session/sessionmanager.cc
index f375dea..11a41ee 100644
--- a/p2p/base/sessionmanager.cc
+++ b/libjingle/session/sessionmanager.cc
@@ -8,11 +8,11 @@
  *  be found in the AUTHORS file in the root of the source tree.
  */
 
-#include "webrtc/p2p/base/sessionmanager.h"
+#include "webrtc/libjingle/session/sessionmanager.h"
 
 #include "webrtc/p2p/base/constants.h"
 #include "webrtc/p2p/base/session.h"
-#include "webrtc/p2p/base/sessionmessages.h"
+#include "webrtc/libjingle/session/sessionmessages.h"
 #include "webrtc/libjingle/xmpp/constants.h"
 #include "webrtc/libjingle/xmpp/jid.h"
 #include "webrtc/base/common.h"
diff --git a/p2p/base/sessionmanager.h b/libjingle/session/sessionmanager.h
similarity index 100%
rename from p2p/base/sessionmanager.h
rename to libjingle/session/sessionmanager.h
diff --git a/p2p/client/sessionmanagertask.h b/libjingle/session/sessionmanagertask.h
similarity index 97%
rename from p2p/client/sessionmanagertask.h
rename to libjingle/session/sessionmanagertask.h
index 04d79d4..d0a0a53 100644
--- a/p2p/client/sessionmanagertask.h
+++ b/libjingle/session/sessionmanagertask.h
@@ -11,7 +11,7 @@
 #ifndef WEBRTC_P2P_CLIENT_SESSIONMANAGERTASK_H_
 #define WEBRTC_P2P_CLIENT_SESSIONMANAGERTASK_H_
 
-#include "webrtc/p2p/base/sessionmanager.h"
+#include "webrtc/libjingle/session/sessionmanager.h"
 #include "webrtc/p2p/client/sessionsendtask.h"
 #include "webrtc/libjingle/xmpp/xmppengine.h"
 #include "webrtc/libjingle/xmpp/xmpptask.h"
diff --git a/p2p/base/sessionmessages.cc b/libjingle/session/sessionmessages.cc
similarity index 99%
rename from p2p/base/sessionmessages.cc
rename to libjingle/session/sessionmessages.cc
index cc63673..45bb2cd 100644
--- a/p2p/base/sessionmessages.cc
+++ b/libjingle/session/sessionmessages.cc
@@ -8,15 +8,15 @@
  *  be found in the AUTHORS file in the root of the source tree.
  */
 
-#include "webrtc/p2p/base/sessionmessages.h"
+#include "webrtc/libjingle/session/sessionmessages.h"
 
 #include <stdio.h>
 #include <string>
 
 #include "webrtc/p2p/base/constants.h"
 #include "webrtc/p2p/base/p2ptransport.h"
-#include "webrtc/p2p/base/parsing.h"
-#include "webrtc/p2p/base/sessionclient.h"
+#include "webrtc/libjingle/session/parsing.h"
+#include "webrtc/libjingle/session/sessionclient.h"
 #include "webrtc/p2p/base/sessiondescription.h"
 #include "webrtc/p2p/base/transport.h"
 #include "webrtc/libjingle/xmllite/xmlconstants.h"
diff --git a/p2p/base/sessionmessages.h b/libjingle/session/sessionmessages.h
similarity index 98%
rename from p2p/base/sessionmessages.h
rename to libjingle/session/sessionmessages.h
index 7b156d4..9a46203 100644
--- a/p2p/base/sessionmessages.h
+++ b/libjingle/session/sessionmessages.h
@@ -16,7 +16,7 @@
 #include <vector>
 
 #include "webrtc/p2p/base/constants.h"
-#include "webrtc/p2p/base/parsing.h"
+#include "webrtc/libjingle/session/parsing.h"
 #include "webrtc/p2p/base/sessiondescription.h"  // Needed to delete contents.
 #include "webrtc/p2p/base/transportinfo.h"
 #include "webrtc/libjingle/xmllite/xmlelement.h"
diff --git a/p2p/client/sessionsendtask.h b/libjingle/session/sessionsendtask.h
similarity index 98%
rename from p2p/client/sessionsendtask.h
rename to libjingle/session/sessionsendtask.h
index 818aa1a..77cea08 100644
--- a/p2p/client/sessionsendtask.h
+++ b/libjingle/session/sessionsendtask.h
@@ -11,7 +11,7 @@
 #ifndef WEBRTC_P2P_CLIENT_SESSIONSENDTASK_H_
 #define WEBRTC_P2P_CLIENT_SESSIONSENDTASK_H_
 
-#include "webrtc/p2p/base/sessionmanager.h"
+#include "webrtc/libjingle/session/sessionmanager.h"
 #include "webrtc/libjingle/xmpp/constants.h"
 #include "webrtc/libjingle/xmpp/xmppclient.h"
 #include "webrtc/libjingle/xmpp/xmppengine.h"
diff --git a/p2p/base/pseudotcp.cc b/libjingle/tunnel/pseudotcp.cc
similarity index 99%
rename from p2p/base/pseudotcp.cc
rename to libjingle/tunnel/pseudotcp.cc
index 0dfe7d8..d564684 100644
--- a/p2p/base/pseudotcp.cc
+++ b/libjingle/tunnel/pseudotcp.cc
@@ -8,7 +8,7 @@
  *  be found in the AUTHORS file in the root of the source tree.
  */
 
-#include "webrtc/p2p/base/pseudotcp.h"
+#include "webrtc/libjingle/tunnel/pseudotcp.h"
 
 #include <stdio.h>
 #include <stdlib.h>
diff --git a/p2p/base/pseudotcp.h b/libjingle/tunnel/pseudotcp.h
similarity index 100%
rename from p2p/base/pseudotcp.h
rename to libjingle/tunnel/pseudotcp.h
diff --git a/p2p/base/pseudotcp_unittest.cc b/libjingle/tunnel/pseudotcp_unittest.cc
similarity index 99%
rename from p2p/base/pseudotcp_unittest.cc
rename to libjingle/tunnel/pseudotcp_unittest.cc
index f5ea7ac..ea2a49a 100644
--- a/p2p/base/pseudotcp_unittest.cc
+++ b/libjingle/tunnel/pseudotcp_unittest.cc
@@ -10,7 +10,7 @@
 
 #include <vector>
 
-#include "webrtc/p2p/base/pseudotcp.h"
+#include "webrtc/libjingle/tunnel/pseudotcp.h"
 #include "webrtc/base/gunit.h"
 #include "webrtc/base/helpers.h"
 #include "webrtc/base/messagehandler.h"
diff --git a/libjingle/tunnel/pseudotcpchannel.cc b/libjingle/tunnel/pseudotcpchannel.cc
new file mode 100644
index 0000000..861e178
--- /dev/null
+++ b/libjingle/tunnel/pseudotcpchannel.cc
@@ -0,0 +1,603 @@
+/*
+ * libjingle
+ * Copyright 2004--2006, 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 "pseudotcpchannel.h"
+#include "webrtc/p2p/base/candidate.h"
+#include "webrtc/p2p/base/transportchannel.h"
+#include "webrtc/base/basictypes.h"
+#include "webrtc/base/common.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/base/stringutils.h"
+
+using namespace rtc;
+
+namespace cricket {
+
+extern const rtc::ConstantLabel SESSION_STATES[];
+
+// MSG_WK_* - worker thread messages
+// MSG_ST_* - stream thread messages
+// MSG_SI_* - signal thread messages
+
+enum {
+  MSG_WK_CLOCK = 1,
+  MSG_WK_PURGE,
+  MSG_ST_EVENT,
+  MSG_SI_DESTROYCHANNEL,
+  MSG_SI_DESTROY,
+};
+
+struct EventData : public MessageData {
+  int event, error;
+  EventData(int ev, int err = 0) : event(ev), error(err) { }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// PseudoTcpChannel::InternalStream
+///////////////////////////////////////////////////////////////////////////////
+
+class PseudoTcpChannel::InternalStream : public StreamInterface {
+public:
+  InternalStream(PseudoTcpChannel* parent);
+  virtual ~InternalStream();
+
+  virtual StreamState GetState() const;
+  virtual StreamResult Read(void* buffer, size_t buffer_len,
+                                       size_t* read, int* error);
+  virtual StreamResult Write(const void* data, size_t data_len,
+                                        size_t* written, int* error);
+  virtual void Close();
+
+private:
+  // parent_ is accessed and modified exclusively on the event thread, to
+  // avoid thread contention.  This means that the PseudoTcpChannel cannot go
+  // away until after it receives a Close() from TunnelStream.
+  PseudoTcpChannel* parent_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// PseudoTcpChannel
+// Member object lifetime summaries:
+//   session_ - passed in constructor, cleared when channel_ goes away.
+//   channel_ - created in Connect, destroyed when session_ or tcp_ goes away.
+//   tcp_ - created in Connect, destroyed when channel_ goes away, or connection
+//     closes.
+//   worker_thread_ - created when channel_ is created, purged when channel_ is
+//     destroyed.
+//   stream_ - created in GetStream, destroyed by owner at arbitrary time.
+//   this - created in constructor, destroyed when worker_thread_ and stream_
+//     are both gone.
+///////////////////////////////////////////////////////////////////////////////
+
+//
+// Signal thread methods
+//
+
+PseudoTcpChannel::PseudoTcpChannel(Thread* stream_thread, Session* session)
+  : signal_thread_(session->session_manager()->signaling_thread()),
+    worker_thread_(NULL),
+    stream_thread_(stream_thread),
+    session_(session), channel_(NULL), tcp_(NULL), stream_(NULL),
+    stream_readable_(false), pending_read_event_(false),
+    ready_to_connect_(false) {
+  ASSERT(signal_thread_->IsCurrent());
+  ASSERT(NULL != session_);
+}
+
+PseudoTcpChannel::~PseudoTcpChannel() {
+  ASSERT(signal_thread_->IsCurrent());
+  ASSERT(worker_thread_ == NULL);
+  ASSERT(session_ == NULL);
+  ASSERT(channel_ == NULL);
+  ASSERT(stream_ == NULL);
+  ASSERT(tcp_ == NULL);
+}
+
+bool PseudoTcpChannel::Connect(const std::string& content_name,
+                               const std::string& channel_name,
+                               int component) {
+  ASSERT(signal_thread_->IsCurrent());
+  CritScope lock(&cs_);
+
+  if (channel_)
+    return false;
+
+  ASSERT(session_ != NULL);
+  worker_thread_ = session_->session_manager()->worker_thread();
+  content_name_ = content_name;
+  channel_ = session_->CreateChannel(
+      content_name, channel_name, component);
+  channel_name_ = channel_name;
+  channel_->SetOption(Socket::OPT_DONTFRAGMENT, 1);
+
+  channel_->SignalDestroyed.connect(this,
+    &PseudoTcpChannel::OnChannelDestroyed);
+  channel_->SignalWritableState.connect(this,
+    &PseudoTcpChannel::OnChannelWritableState);
+  channel_->SignalReadPacket.connect(this,
+    &PseudoTcpChannel::OnChannelRead);
+  channel_->SignalRouteChange.connect(this,
+    &PseudoTcpChannel::OnChannelConnectionChanged);
+
+  ASSERT(tcp_ == NULL);
+  tcp_ = new PseudoTcp(this, 0);
+  if (session_->initiator()) {
+    // Since we may try several protocols and network adapters that won't work,
+    // waiting until we get our first writable notification before initiating
+    // TCP negotiation.
+    ready_to_connect_ = true;
+  }
+
+  return true;
+}
+
+StreamInterface* PseudoTcpChannel::GetStream() {
+  ASSERT(signal_thread_->IsCurrent());
+  CritScope lock(&cs_);
+  ASSERT(NULL != session_);
+  if (!stream_)
+    stream_ = new PseudoTcpChannel::InternalStream(this);
+  //TODO("should we disallow creation of new stream at some point?");
+  return stream_;
+}
+
+void PseudoTcpChannel::OnChannelDestroyed(TransportChannel* channel) {
+  LOG_F(LS_INFO) << "(" << channel->component() << ")";
+  ASSERT(signal_thread_->IsCurrent());
+  CritScope lock(&cs_);
+  ASSERT(channel == channel_);
+  signal_thread_->Clear(this, MSG_SI_DESTROYCHANNEL);
+  // When MSG_WK_PURGE is received, we know there will be no more messages from
+  // the worker thread.
+  worker_thread_->Clear(this, MSG_WK_CLOCK);
+  worker_thread_->Post(this, MSG_WK_PURGE);
+  session_ = NULL;
+  channel_ = NULL;
+  if ((stream_ != NULL)
+      && ((tcp_ == NULL) || (tcp_->State() != PseudoTcp::TCP_CLOSED)))
+    stream_thread_->Post(this, MSG_ST_EVENT, new EventData(SE_CLOSE, 0));
+  if (tcp_) {
+    tcp_->Close(true);
+    AdjustClock();
+  }
+  SignalChannelClosed(this);
+}
+
+void PseudoTcpChannel::OnSessionTerminate(Session* session) {
+  // When the session terminates before we even connected
+  CritScope lock(&cs_);
+  if (session_ != NULL && channel_ == NULL) {
+    ASSERT(session == session_);
+    ASSERT(worker_thread_ == NULL);
+    ASSERT(tcp_ == NULL);
+    LOG(LS_INFO) << "Destroying unconnected PseudoTcpChannel";
+    session_ = NULL;
+    if (stream_ != NULL)
+      stream_thread_->Post(this, MSG_ST_EVENT, new EventData(SE_CLOSE, -1));
+  }
+
+  // Even though session_ is being destroyed, we mustn't clear the pointer,
+  // since we'll need it to tear down channel_.
+  //
+  // TODO: Is it always the case that if channel_ != NULL then we'll get
+  // a channel-destroyed notification?
+}
+
+void PseudoTcpChannel::GetOption(PseudoTcp::Option opt, int* value) {
+  ASSERT(signal_thread_->IsCurrent());
+  CritScope lock(&cs_);
+  ASSERT(tcp_ != NULL);
+  tcp_->GetOption(opt, value);
+}
+
+void PseudoTcpChannel::SetOption(PseudoTcp::Option opt, int value) {
+  ASSERT(signal_thread_->IsCurrent());
+  CritScope lock(&cs_);
+  ASSERT(tcp_ != NULL);
+  tcp_->SetOption(opt, value);
+}
+
+//
+// Stream thread methods
+//
+
+StreamState PseudoTcpChannel::GetState() const {
+  ASSERT(stream_ != NULL && stream_thread_->IsCurrent());
+  CritScope lock(&cs_);
+  if (!session_)
+    return SS_CLOSED;
+  if (!tcp_)
+    return SS_OPENING;
+  switch (tcp_->State()) {
+    case PseudoTcp::TCP_LISTEN:
+    case PseudoTcp::TCP_SYN_SENT:
+    case PseudoTcp::TCP_SYN_RECEIVED:
+      return SS_OPENING;
+    case PseudoTcp::TCP_ESTABLISHED:
+      return SS_OPEN;
+    case PseudoTcp::TCP_CLOSED:
+    default:
+      return SS_CLOSED;
+  }
+}
+
+StreamResult PseudoTcpChannel::Read(void* buffer, size_t buffer_len,
+                                    size_t* read, int* error) {
+  ASSERT(stream_ != NULL && stream_thread_->IsCurrent());
+  CritScope lock(&cs_);
+  if (!tcp_)
+    return SR_BLOCK;
+
+  stream_readable_ = false;
+  int result = tcp_->Recv(static_cast<char*>(buffer), buffer_len);
+  //LOG_F(LS_VERBOSE) << "Recv returned: " << result;
+  if (result > 0) {
+    if (read)
+      *read = result;
+    // PseudoTcp doesn't currently support repeated Readable signals.  Simulate
+    // them here.
+    stream_readable_ = true;
+    if (!pending_read_event_) {
+      pending_read_event_ = true;
+      stream_thread_->Post(this, MSG_ST_EVENT, new EventData(SE_READ), true);
+    }
+    return SR_SUCCESS;
+  } else if (IsBlockingError(tcp_->GetError())) {
+    return SR_BLOCK;
+  } else {
+    if (error)
+      *error = tcp_->GetError();
+    return SR_ERROR;
+  }
+  // This spot is never reached.
+}
+
+StreamResult PseudoTcpChannel::Write(const void* data, size_t data_len,
+                                     size_t* written, int* error) {
+  ASSERT(stream_ != NULL && stream_thread_->IsCurrent());
+  CritScope lock(&cs_);
+  if (!tcp_)
+    return SR_BLOCK;
+  int result = tcp_->Send(static_cast<const char*>(data), data_len);
+  //LOG_F(LS_VERBOSE) << "Send returned: " << result;
+  if (result > 0) {
+    if (written)
+      *written = result;
+    return SR_SUCCESS;
+  } else if (IsBlockingError(tcp_->GetError())) {
+    return SR_BLOCK;
+  } else {
+    if (error)
+      *error = tcp_->GetError();
+    return SR_ERROR;
+  }
+  // This spot is never reached.
+}
+
+void PseudoTcpChannel::Close() {
+  ASSERT(stream_ != NULL && stream_thread_->IsCurrent());
+  CritScope lock(&cs_);
+  stream_ = NULL;
+  // Clear out any pending event notifications
+  stream_thread_->Clear(this, MSG_ST_EVENT);
+  if (tcp_) {
+    tcp_->Close(false);
+    AdjustClock();
+  } else {
+    CheckDestroy();
+  }
+}
+
+//
+// Worker thread methods
+//
+
+void PseudoTcpChannel::OnChannelWritableState(TransportChannel* channel) {
+  LOG_F(LS_VERBOSE) << "[" << channel_name_ << "]";
+  ASSERT(worker_thread_->IsCurrent());
+  CritScope lock(&cs_);
+  if (!channel_) {
+    LOG_F(LS_WARNING) << "NULL channel";
+    return;
+  }
+  ASSERT(channel == channel_);
+  if (!tcp_) {
+    LOG_F(LS_WARNING) << "NULL tcp";
+    return;
+  }
+  if (!ready_to_connect_ || !channel->writable())
+    return;
+
+  ready_to_connect_ = false;
+  tcp_->Connect();
+  AdjustClock();
+}
+
+void PseudoTcpChannel::OnChannelRead(TransportChannel* channel,
+                                     const char* data, size_t size,
+                                     const rtc::PacketTime& packet_time,
+                                     int flags) {
+  //LOG_F(LS_VERBOSE) << "(" << size << ")";
+  ASSERT(worker_thread_->IsCurrent());
+  CritScope lock(&cs_);
+  if (!channel_) {
+    LOG_F(LS_WARNING) << "NULL channel";
+    return;
+  }
+  ASSERT(channel == channel_);
+  if (!tcp_) {
+    LOG_F(LS_WARNING) << "NULL tcp";
+    return;
+  }
+  tcp_->NotifyPacket(data, size);
+  AdjustClock();
+}
+
+void PseudoTcpChannel::OnChannelConnectionChanged(TransportChannel* channel,
+                                                  const Candidate& candidate) {
+  LOG_F(LS_VERBOSE) << "[" << channel_name_ << "]";
+  ASSERT(worker_thread_->IsCurrent());
+  CritScope lock(&cs_);
+  if (!channel_) {
+    LOG_F(LS_WARNING) << "NULL channel";
+    return;
+  }
+  ASSERT(channel == channel_);
+  if (!tcp_) {
+    LOG_F(LS_WARNING) << "NULL tcp";
+    return;
+  }
+
+  uint16 mtu = 1280;  // safe default
+  int family = candidate.address().family();
+  Socket* socket =
+      worker_thread_->socketserver()->CreateAsyncSocket(family, SOCK_DGRAM);
+  rtc::scoped_ptr<Socket> mtu_socket(socket);
+  if (socket == NULL) {
+    LOG_F(LS_WARNING) << "Couldn't create socket while estimating MTU.";
+  } else {
+    if (mtu_socket->Connect(candidate.address()) < 0 ||
+        mtu_socket->EstimateMTU(&mtu) < 0) {
+      LOG_F(LS_WARNING) << "Failed to estimate MTU, error="
+                        << mtu_socket->GetError();
+    }
+  }
+
+  LOG_F(LS_VERBOSE) << "Using MTU of " << mtu << " bytes";
+  tcp_->NotifyMTU(mtu);
+  AdjustClock();
+}
+
+void PseudoTcpChannel::OnTcpOpen(PseudoTcp* tcp) {
+  LOG_F(LS_VERBOSE) << "[" << channel_name_ << "]";
+  ASSERT(cs_.CurrentThreadIsOwner());
+  ASSERT(worker_thread_->IsCurrent());
+  ASSERT(tcp == tcp_);
+  if (stream_) {
+    stream_readable_ = true;
+    pending_read_event_ = true;
+    stream_thread_->Post(this, MSG_ST_EVENT,
+                         new EventData(SE_OPEN | SE_READ | SE_WRITE));
+  }
+}
+
+void PseudoTcpChannel::OnTcpReadable(PseudoTcp* tcp) {
+  //LOG_F(LS_VERBOSE);
+  ASSERT(cs_.CurrentThreadIsOwner());
+  ASSERT(worker_thread_->IsCurrent());
+  ASSERT(tcp == tcp_);
+  if (stream_) {
+    stream_readable_ = true;
+    if (!pending_read_event_) {
+      pending_read_event_ = true;
+      stream_thread_->Post(this, MSG_ST_EVENT, new EventData(SE_READ));
+    }
+  }
+}
+
+void PseudoTcpChannel::OnTcpWriteable(PseudoTcp* tcp) {
+  //LOG_F(LS_VERBOSE);
+  ASSERT(cs_.CurrentThreadIsOwner());
+  ASSERT(worker_thread_->IsCurrent());
+  ASSERT(tcp == tcp_);
+  if (stream_)
+    stream_thread_->Post(this, MSG_ST_EVENT, new EventData(SE_WRITE));
+}
+
+void PseudoTcpChannel::OnTcpClosed(PseudoTcp* tcp, uint32 nError) {
+  LOG_F(LS_VERBOSE) << "[" << channel_name_ << "]";
+  ASSERT(cs_.CurrentThreadIsOwner());
+  ASSERT(worker_thread_->IsCurrent());
+  ASSERT(tcp == tcp_);
+  if (stream_)
+    stream_thread_->Post(this, MSG_ST_EVENT, new EventData(SE_CLOSE, nError));
+}
+
+//
+// Multi-thread methods
+//
+
+void PseudoTcpChannel::OnMessage(Message* pmsg) {
+  if (pmsg->message_id == MSG_WK_CLOCK) {
+
+    ASSERT(worker_thread_->IsCurrent());
+    //LOG(LS_INFO) << "PseudoTcpChannel::OnMessage(MSG_WK_CLOCK)";
+    CritScope lock(&cs_);
+    if (tcp_) {
+      tcp_->NotifyClock(PseudoTcp::Now());
+      AdjustClock(false);
+    }
+
+  } else if (pmsg->message_id == MSG_WK_PURGE) {
+
+    ASSERT(worker_thread_->IsCurrent());
+    LOG_F(LS_INFO) << "(MSG_WK_PURGE)";
+    // At this point, we know there are no additional worker thread messages.
+    CritScope lock(&cs_);
+    ASSERT(NULL == session_);
+    ASSERT(NULL == channel_);
+    worker_thread_ = NULL;
+    CheckDestroy();
+
+  } else if (pmsg->message_id == MSG_ST_EVENT) {
+
+    ASSERT(stream_thread_->IsCurrent());
+    //LOG(LS_INFO) << "PseudoTcpChannel::OnMessage(MSG_ST_EVENT, "
+    //             << data->event << ", " << data->error << ")";
+    ASSERT(stream_ != NULL);
+    EventData* data = static_cast<EventData*>(pmsg->pdata);
+    if (data->event & SE_READ) {
+      CritScope lock(&cs_);
+      pending_read_event_ = false;
+    }
+    stream_->SignalEvent(stream_, data->event, data->error);
+    delete data;
+
+  } else if (pmsg->message_id == MSG_SI_DESTROYCHANNEL) {
+
+    ASSERT(signal_thread_->IsCurrent());
+    LOG_F(LS_INFO) << "(MSG_SI_DESTROYCHANNEL)";
+    ASSERT(session_ != NULL);
+    ASSERT(channel_ != NULL);
+    session_->DestroyChannel(content_name_, channel_->component());
+
+  } else if (pmsg->message_id == MSG_SI_DESTROY) {
+
+    ASSERT(signal_thread_->IsCurrent());
+    LOG_F(LS_INFO) << "(MSG_SI_DESTROY)";
+    // The message queue is empty, so it is safe to destroy ourselves.
+    delete this;
+
+  } else {
+    ASSERT(false);
+  }
+}
+
+IPseudoTcpNotify::WriteResult PseudoTcpChannel::TcpWritePacket(
+    PseudoTcp* tcp, const char* buffer, size_t len) {
+  ASSERT(cs_.CurrentThreadIsOwner());
+  ASSERT(tcp == tcp_);
+  ASSERT(NULL != channel_);
+  rtc::PacketOptions packet_options;
+  int sent = channel_->SendPacket(buffer, len, packet_options);
+  if (sent > 0) {
+    //LOG_F(LS_VERBOSE) << "(" << sent << ") Sent";
+    return IPseudoTcpNotify::WR_SUCCESS;
+  } else if (IsBlockingError(channel_->GetError())) {
+    LOG_F(LS_VERBOSE) << "Blocking";
+    return IPseudoTcpNotify::WR_SUCCESS;
+  } else if (channel_->GetError() == EMSGSIZE) {
+    LOG_F(LS_ERROR) << "EMSGSIZE";
+    return IPseudoTcpNotify::WR_TOO_LARGE;
+  } else {
+    PLOG(LS_ERROR, channel_->GetError()) << "PseudoTcpChannel::TcpWritePacket";
+    ASSERT(false);
+    return IPseudoTcpNotify::WR_FAIL;
+  }
+}
+
+void PseudoTcpChannel::AdjustClock(bool clear) {
+  ASSERT(cs_.CurrentThreadIsOwner());
+  ASSERT(NULL != tcp_);
+
+  long timeout = 0;
+  if (tcp_->GetNextClock(PseudoTcp::Now(), timeout)) {
+    ASSERT(NULL != channel_);
+    // Reset the next clock, by clearing the old and setting a new one.
+    if (clear)
+      worker_thread_->Clear(this, MSG_WK_CLOCK);
+    worker_thread_->PostDelayed(_max(timeout, 0L), this, MSG_WK_CLOCK);
+    return;
+  }
+
+  delete tcp_;
+  tcp_ = NULL;
+  ready_to_connect_ = false;
+
+  if (channel_) {
+    // If TCP has failed, no need for channel_ anymore
+    signal_thread_->Post(this, MSG_SI_DESTROYCHANNEL);
+  }
+}
+
+void PseudoTcpChannel::CheckDestroy() {
+  ASSERT(cs_.CurrentThreadIsOwner());
+  if ((worker_thread_ != NULL) || (stream_ != NULL))
+    return;
+  signal_thread_->Post(this, MSG_SI_DESTROY);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// PseudoTcpChannel::InternalStream
+///////////////////////////////////////////////////////////////////////////////
+
+PseudoTcpChannel::InternalStream::InternalStream(PseudoTcpChannel* parent)
+  : parent_(parent) {
+}
+
+PseudoTcpChannel::InternalStream::~InternalStream() {
+  Close();
+}
+
+StreamState PseudoTcpChannel::InternalStream::GetState() const {
+  if (!parent_)
+    return SS_CLOSED;
+  return parent_->GetState();
+}
+
+StreamResult PseudoTcpChannel::InternalStream::Read(
+    void* buffer, size_t buffer_len, size_t* read, int* error) {
+  if (!parent_) {
+    if (error)
+      *error = ENOTCONN;
+    return SR_ERROR;
+  }
+  return parent_->Read(buffer, buffer_len, read, error);
+}
+
+StreamResult PseudoTcpChannel::InternalStream::Write(
+    const void* data, size_t data_len,  size_t* written, int* error) {
+  if (!parent_) {
+    if (error)
+      *error = ENOTCONN;
+    return SR_ERROR;
+  }
+  return parent_->Write(data, data_len, written, error);
+}
+
+void PseudoTcpChannel::InternalStream::Close() {
+  if (!parent_)
+    return;
+  parent_->Close();
+  parent_ = NULL;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace cricket
diff --git a/libjingle/tunnel/pseudotcpchannel.h b/libjingle/tunnel/pseudotcpchannel.h
new file mode 100644
index 0000000..3310c09
--- /dev/null
+++ b/libjingle/tunnel/pseudotcpchannel.h
@@ -0,0 +1,140 @@
+/*
+ * libjingle
+ * Copyright 2004--2006, 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.
+ */
+
+#ifndef TALK_SESSION_TUNNEL_PSEUDOTCPCHANNEL_H_
+#define TALK_SESSION_TUNNEL_PSEUDOTCPCHANNEL_H_
+
+#include "webrtc/libjingle/tunnel/pseudotcp.h"
+#include "webrtc/p2p/base/session.h"
+#include "webrtc/base/criticalsection.h"
+#include "webrtc/base/messagequeue.h"
+#include "webrtc/base/stream.h"
+
+namespace rtc {
+class Thread;
+}
+
+namespace cricket {
+
+class Candidate;
+class TransportChannel;
+
+///////////////////////////////////////////////////////////////////////////////
+// PseudoTcpChannel
+// Note: The PseudoTcpChannel must persist until both of:
+// 1) The StreamInterface provided via GetStream has been closed.
+//    This is tracked via non-null stream_.
+// 2) The PseudoTcp session has completed.
+//    This is tracked via non-null worker_thread_.  When PseudoTcp is done,
+//    the TransportChannel is signalled to tear-down.  Once the channel is
+//    torn down, the worker thread is purged.
+// These indicators are checked by CheckDestroy, invoked whenever one of them
+// changes.
+///////////////////////////////////////////////////////////////////////////////
+// PseudoTcpChannel::GetStream
+// Note: The stream pointer returned by GetStream is owned by the caller.
+// They can close & immediately delete the stream while PseudoTcpChannel still
+// has cleanup work to do.  They can also close the stream but not delete it
+// until long after PseudoTcpChannel has finished.  We must cope with both.
+///////////////////////////////////////////////////////////////////////////////
+
+class PseudoTcpChannel
+    : public IPseudoTcpNotify,
+      public rtc::MessageHandler,
+      public sigslot::has_slots<> {
+ public:
+  // Signal thread methods
+  PseudoTcpChannel(rtc::Thread* stream_thread,
+                   Session* session);
+
+  bool Connect(const std::string& content_name,
+               const std::string& channel_name,
+               int component);
+  rtc::StreamInterface* GetStream();
+
+  sigslot::signal1<PseudoTcpChannel*> SignalChannelClosed;
+
+  // Call this when the Session used to create this channel is being torn
+  // down, to ensure that things get cleaned up properly.
+  void OnSessionTerminate(Session* session);
+
+  // See the PseudoTcp class for available options.
+  void GetOption(PseudoTcp::Option opt, int* value);
+  void SetOption(PseudoTcp::Option opt, int value);
+
+ private:
+  class InternalStream;
+  friend class InternalStream;
+
+  virtual ~PseudoTcpChannel();
+
+  // Stream thread methods
+  rtc::StreamState GetState() const;
+  rtc::StreamResult Read(void* buffer, size_t buffer_len,
+                               size_t* read, int* error);
+  rtc::StreamResult Write(const void* data, size_t data_len,
+                                size_t* written, int* error);
+  void Close();
+
+  // Multi-thread methods
+  void OnMessage(rtc::Message* pmsg);
+  void AdjustClock(bool clear = true);
+  void CheckDestroy();
+
+  // Signal thread methods
+  void OnChannelDestroyed(TransportChannel* channel);
+
+  // Worker thread methods
+  void OnChannelWritableState(TransportChannel* channel);
+  void OnChannelRead(TransportChannel* channel, const char* data, size_t size,
+                     const rtc::PacketTime& packet_time, int flags);
+  void OnChannelConnectionChanged(TransportChannel* channel,
+                                  const Candidate& candidate);
+
+  virtual void OnTcpOpen(PseudoTcp* ptcp);
+  virtual void OnTcpReadable(PseudoTcp* ptcp);
+  virtual void OnTcpWriteable(PseudoTcp* ptcp);
+  virtual void OnTcpClosed(PseudoTcp* ptcp, uint32 nError);
+  virtual IPseudoTcpNotify::WriteResult TcpWritePacket(PseudoTcp* tcp,
+                                                       const char* buffer,
+                                                       size_t len);
+
+  rtc::Thread* signal_thread_, * worker_thread_, * stream_thread_;
+  Session* session_;
+  TransportChannel* channel_;
+  std::string content_name_;
+  std::string channel_name_;
+  PseudoTcp* tcp_;
+  InternalStream* stream_;
+  bool stream_readable_, pending_read_event_;
+  bool ready_to_connect_;
+  mutable rtc::CriticalSection cs_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_TUNNEL_PSEUDOTCPCHANNEL_H_
diff --git a/libjingle/tunnel/securetunnelsessionclient.cc b/libjingle/tunnel/securetunnelsessionclient.cc
new file mode 100644
index 0000000..a9e676a
--- /dev/null
+++ b/libjingle/tunnel/securetunnelsessionclient.cc
@@ -0,0 +1,397 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, 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.
+ */
+
+// SecureTunnelSessionClient and SecureTunnelSession implementation.
+
+#include "webrtc/p2p/base/transportchannel.h"
+#include "webrtc/libjingle/tunnel/pseudotcpchannel.h"
+#include "webrtc/libjingle/tunnel/securetunnelsessionclient.h"
+#include "webrtc/libjingle/xmllite/xmlelement.h"
+#include "webrtc/base/basicdefs.h"
+#include "webrtc/base/basictypes.h"
+#include "webrtc/base/common.h"
+#include "webrtc/base/helpers.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/sslidentity.h"
+#include "webrtc/base/sslstreamadapter.h"
+#include "webrtc/base/stringutils.h"
+
+namespace cricket {
+
+// XML elements and namespaces for XMPP stanzas used in content exchanges.
+
+const char NS_SECURE_TUNNEL[] = "http://www.google.com/talk/securetunnel";
+const buzz::StaticQName QN_SECURE_TUNNEL_DESCRIPTION =
+    { NS_SECURE_TUNNEL, "description" };
+const buzz::StaticQName QN_SECURE_TUNNEL_TYPE =
+    { NS_SECURE_TUNNEL, "type" };
+const buzz::StaticQName QN_SECURE_TUNNEL_CLIENT_CERT =
+    { NS_SECURE_TUNNEL, "client-cert" };
+const buzz::StaticQName QN_SECURE_TUNNEL_SERVER_CERT =
+    { NS_SECURE_TUNNEL, "server-cert" };
+const char CN_SECURE_TUNNEL[] = "securetunnel";
+
+// SecureTunnelContentDescription
+
+// TunnelContentDescription is extended to hold string forms of the
+// client and server certificate, PEM encoded.
+
+struct SecureTunnelContentDescription : public ContentDescription {
+  std::string description;
+  std::string client_pem_certificate;
+  std::string server_pem_certificate;
+
+  SecureTunnelContentDescription(const std::string& desc,
+                                 const std::string& client_pem_cert,
+                                 const std::string& server_pem_cert)
+      : description(desc),
+        client_pem_certificate(client_pem_cert),
+        server_pem_certificate(server_pem_cert) {
+  }
+  virtual ContentDescription* Copy() const {
+    return new SecureTunnelContentDescription(*this);
+  }
+};
+
+// SecureTunnelSessionClient
+
+SecureTunnelSessionClient::SecureTunnelSessionClient(
+    const buzz::Jid& jid, SessionManager* manager)
+    : TunnelSessionClient(jid, manager, NS_SECURE_TUNNEL) {
+}
+
+void SecureTunnelSessionClient::SetIdentity(rtc::SSLIdentity* identity) {
+  ASSERT(identity_.get() == NULL);
+  identity_.reset(identity);
+}
+
+bool SecureTunnelSessionClient::GenerateIdentity() {
+  ASSERT(identity_.get() == NULL);
+  identity_.reset(rtc::SSLIdentity::Generate(
+      // The name on the certificate does not matter: the peer will
+      // make sure the cert it gets during SSL negotiation matches the
+      // one it got from XMPP. It would be neat to put something
+      // recognizable in there such as the JID, except this will show
+      // in clear during the SSL negotiation and so it could be a
+      // privacy issue. Specifying an empty string here causes
+      // it to use a random string.
+#ifdef _DEBUG
+      jid().Str()
+#else
+      ""
+#endif
+      ));
+  if (identity_.get() == NULL) {
+    LOG(LS_ERROR) << "Failed to generate SSL identity";
+    return false;
+  }
+  return true;
+}
+
+rtc::SSLIdentity& SecureTunnelSessionClient::GetIdentity() const {
+  ASSERT(identity_.get() != NULL);
+  return *identity_;
+}
+
+// Parses a certificate from a PEM encoded string.
+// Returns NULL on failure.
+// The caller is responsible for freeing the returned object.
+static rtc::SSLCertificate* ParseCertificate(
+    const std::string& pem_cert) {
+  if (pem_cert.empty())
+    return NULL;
+  return rtc::SSLCertificate::FromPEMString(pem_cert);
+}
+
+TunnelSession* SecureTunnelSessionClient::MakeTunnelSession(
+    Session* session, rtc::Thread* stream_thread,
+    TunnelSessionRole role) {
+  return new SecureTunnelSession(this, session, stream_thread, role);
+}
+
+bool FindSecureTunnelContent(const cricket::SessionDescription* sdesc,
+                             std::string* name,
+                             const SecureTunnelContentDescription** content) {
+  const ContentInfo* cinfo = sdesc->FirstContentByType(NS_SECURE_TUNNEL);
+  if (cinfo == NULL)
+    return false;
+
+  *name = cinfo->name;
+  *content = static_cast<const SecureTunnelContentDescription*>(
+      cinfo->description);
+  return true;
+}
+
+void SecureTunnelSessionClient::OnIncomingTunnel(const buzz::Jid &jid,
+                                                 Session *session) {
+  std::string content_name;
+  const SecureTunnelContentDescription* content = NULL;
+  if (!FindSecureTunnelContent(session->remote_description(),
+                               &content_name, &content)) {
+    ASSERT(false);
+  }
+
+  // Validate the certificate
+  rtc::scoped_ptr<rtc::SSLCertificate> peer_cert(
+      ParseCertificate(content->client_pem_certificate));
+  if (peer_cert.get() == NULL) {
+    LOG(LS_ERROR)
+        << "Rejecting incoming secure tunnel with invalid cetificate";
+    DeclineTunnel(session);
+    return;
+  }
+  // If there were a convenient place we could have cached the
+  // peer_cert so as not to have to parse it a second time when
+  // configuring the tunnel.
+  SignalIncomingTunnel(this, jid, content->description, session);
+}
+
+// The XML representation of a session initiation request (XMPP IQ),
+// containing the initiator's SecureTunnelContentDescription,
+// looks something like this:
+// <iq from="INITIATOR@gmail.com/pcpE101B7F4"
+//       to="RECIPIENT@gmail.com/pcp8B87F0A3"
+//       type="set" id="3">
+//   <session xmlns="http://www.google.com/session"
+//       type="initiate" id="2508605813"
+//       initiator="INITIATOR@gmail.com/pcpE101B7F4">
+//     <description xmlns="http://www.google.com/talk/securetunnel">
+//       <type>send:filename</type>
+//       <client-cert>
+//         -----BEGIN CERTIFICATE-----
+//         INITIATOR'S CERTIFICATE IN PERM FORMAT (ASCII GIBBERISH)
+//         -----END CERTIFICATE-----
+//       </client-cert>
+//     </description>
+//     <transport xmlns="http://www.google.com/transport/p2p"/>
+//   </session>
+// </iq>
+
+// The session accept iq, containing the recipient's certificate and
+// echoing the initiator's certificate, looks something like this:
+// <iq from="RECIPIENT@gmail.com/pcpE101B7F4"
+//     to="INITIATOR@gmail.com/pcpE101B7F4"
+//     type="set" id="5">
+//   <session xmlns="http://www.google.com/session"
+//       type="accept" id="2508605813"
+//       initiator="sdoyon911@gmail.com/pcpE101B7F4">
+//     <description xmlns="http://www.google.com/talk/securetunnel">
+//       <type>send:FILENAME</type>
+//       <client-cert>
+//         -----BEGIN CERTIFICATE-----
+//         INITIATOR'S CERTIFICATE IN PERM FORMAT (ASCII GIBBERISH)
+//         -----END CERTIFICATE-----
+//       </client-cert>
+//       <server-cert>
+//         -----BEGIN CERTIFICATE-----
+//         RECIPIENT'S CERTIFICATE IN PERM FORMAT (ASCII GIBBERISH)
+//         -----END CERTIFICATE-----
+//       </server-cert>
+//     </description>
+//   </session>
+// </iq>
+
+
+bool SecureTunnelSessionClient::ParseContent(SignalingProtocol protocol,
+                                             const buzz::XmlElement* elem,
+                                             ContentDescription** content,
+                                             ParseError* error) {
+  const buzz::XmlElement* type_elem = elem->FirstNamed(QN_SECURE_TUNNEL_TYPE);
+
+  if (type_elem == NULL)
+    // Missing mandatory XML element.
+    return false;
+
+  // Here we consider the certificate components to be optional. In
+  // practice the client certificate is always present, and the server
+  // certificate is initially missing from the session description
+  // sent during session initiation. OnAccept() will enforce that we
+  // have a certificate for our peer.
+  const buzz::XmlElement* client_cert_elem =
+      elem->FirstNamed(QN_SECURE_TUNNEL_CLIENT_CERT);
+  const buzz::XmlElement* server_cert_elem =
+      elem->FirstNamed(QN_SECURE_TUNNEL_SERVER_CERT);
+  *content = new SecureTunnelContentDescription(
+      type_elem->BodyText(),
+      client_cert_elem ? client_cert_elem->BodyText() : "",
+      server_cert_elem ? server_cert_elem->BodyText() : "");
+  return true;
+}
+
+bool SecureTunnelSessionClient::WriteContent(
+    SignalingProtocol protocol, const ContentDescription* untyped_content,
+    buzz::XmlElement** elem, WriteError* error) {
+  const SecureTunnelContentDescription* content =
+      static_cast<const SecureTunnelContentDescription*>(untyped_content);
+
+  buzz::XmlElement* root =
+      new buzz::XmlElement(QN_SECURE_TUNNEL_DESCRIPTION, true);
+  buzz::XmlElement* type_elem = new buzz::XmlElement(QN_SECURE_TUNNEL_TYPE);
+  type_elem->SetBodyText(content->description);
+  root->AddElement(type_elem);
+  if (!content->client_pem_certificate.empty()) {
+    buzz::XmlElement* client_cert_elem =
+        new buzz::XmlElement(QN_SECURE_TUNNEL_CLIENT_CERT);
+    client_cert_elem->SetBodyText(content->client_pem_certificate);
+    root->AddElement(client_cert_elem);
+  }
+  if (!content->server_pem_certificate.empty()) {
+    buzz::XmlElement* server_cert_elem =
+        new buzz::XmlElement(QN_SECURE_TUNNEL_SERVER_CERT);
+    server_cert_elem->SetBodyText(content->server_pem_certificate);
+    root->AddElement(server_cert_elem);
+  }
+  *elem = root;
+  return true;
+}
+
+SessionDescription* NewSecureTunnelSessionDescription(
+    const std::string& content_name, ContentDescription* content) {
+  SessionDescription* sdesc = new SessionDescription();
+  sdesc->AddContent(content_name, NS_SECURE_TUNNEL, content);
+  return sdesc;
+}
+
+SessionDescription* SecureTunnelSessionClient::CreateOffer(
+    const buzz::Jid &jid, const std::string &description) {
+  // We are the initiator so we are the client. Put our cert into the
+  // description.
+  std::string pem_cert = GetIdentity().certificate().ToPEMString();
+  return NewSecureTunnelSessionDescription(
+      CN_SECURE_TUNNEL,
+      new SecureTunnelContentDescription(description, pem_cert, ""));
+}
+
+SessionDescription* SecureTunnelSessionClient::CreateAnswer(
+    const SessionDescription* offer) {
+  std::string content_name;
+  const SecureTunnelContentDescription* offer_tunnel = NULL;
+  if (!FindSecureTunnelContent(offer, &content_name, &offer_tunnel))
+    return NULL;
+
+  // We are accepting a session request. We need to add our cert, the
+  // server cert, into the description. The client cert was validated
+  // in OnIncomingTunnel().
+  ASSERT(!offer_tunnel->client_pem_certificate.empty());
+  return NewSecureTunnelSessionDescription(
+      content_name,
+      new SecureTunnelContentDescription(
+          offer_tunnel->description,
+          offer_tunnel->client_pem_certificate,
+          GetIdentity().certificate().ToPEMString()));
+}
+
+// SecureTunnelSession
+
+SecureTunnelSession::SecureTunnelSession(
+    SecureTunnelSessionClient* client, Session* session,
+    rtc::Thread* stream_thread, TunnelSessionRole role)
+    : TunnelSession(client, session, stream_thread),
+      role_(role) {
+}
+
+rtc::StreamInterface* SecureTunnelSession::MakeSecureStream(
+    rtc::StreamInterface* stream) {
+  rtc::SSLStreamAdapter* ssl_stream =
+      rtc::SSLStreamAdapter::Create(stream);
+  rtc::SSLIdentity* identity =
+      static_cast<SecureTunnelSessionClient*>(client_)->
+      GetIdentity().GetReference();
+  ssl_stream->SetIdentity(identity);
+  if (role_ == RESPONDER)
+    ssl_stream->SetServerRole();
+  ssl_stream->StartSSLWithPeer();
+
+  // SSL negotiation will start on the stream as soon as it
+  // opens. However our SSLStreamAdapter still hasn't been told what
+  // certificate to allow for our peer. If we are the initiator, we do
+  // not have the peer's certificate yet: we will obtain it from the
+  // session accept message which we will receive later (see
+  // OnAccept()). We won't Connect() the PseudoTcpChannel until we get
+  // that, so the stream will stay closed until then.  Keep a handle
+  // on the streem so we can configure the peer certificate later.
+  ssl_stream_reference_.reset(new rtc::StreamReference(ssl_stream));
+  return ssl_stream_reference_->NewReference();
+}
+
+rtc::StreamInterface* SecureTunnelSession::GetStream() {
+  ASSERT(channel_ != NULL);
+  ASSERT(ssl_stream_reference_.get() == NULL);
+  return MakeSecureStream(channel_->GetStream());
+}
+
+void SecureTunnelSession::OnAccept() {
+  // We have either sent or received a session accept: it's time to
+  // connect the tunnel. First we must set the peer certificate.
+  ASSERT(channel_ != NULL);
+  ASSERT(session_ != NULL);
+  std::string content_name;
+  const SecureTunnelContentDescription* remote_tunnel = NULL;
+  if (!FindSecureTunnelContent(session_->remote_description(),
+                               &content_name, &remote_tunnel)) {
+    session_->Reject(STR_TERMINATE_INCOMPATIBLE_PARAMETERS);
+    return;
+  }
+
+  const std::string& cert_pem =
+      role_ == INITIATOR ? remote_tunnel->server_pem_certificate :
+                           remote_tunnel->client_pem_certificate;
+  rtc::scoped_ptr<rtc::SSLCertificate> peer_cert(
+      ParseCertificate(cert_pem));
+  if (peer_cert == NULL) {
+    ASSERT(role_ == INITIATOR);  // when RESPONDER we validated it earlier
+    LOG(LS_ERROR)
+        << "Rejecting secure tunnel accept with invalid cetificate";
+    session_->Reject(STR_TERMINATE_INCOMPATIBLE_PARAMETERS);
+    return;
+  }
+  ASSERT(ssl_stream_reference_.get() != NULL);
+  rtc::SSLStreamAdapter* ssl_stream =
+      static_cast<rtc::SSLStreamAdapter*>(
+          ssl_stream_reference_->GetStream());
+
+  std::string algorithm;
+  if (!peer_cert->GetSignatureDigestAlgorithm(&algorithm)) {
+    LOG(LS_ERROR) << "Failed to get the algorithm for the peer cert signature";
+    return;
+  }
+  unsigned char digest[rtc::MessageDigest::kMaxSize];
+  size_t digest_len;
+  peer_cert->ComputeDigest(algorithm, digest, ARRAY_SIZE(digest), &digest_len);
+  ssl_stream->SetPeerCertificateDigest(algorithm, digest, digest_len);
+
+  // We no longer need our handle to the ssl stream.
+  ssl_stream_reference_.reset();
+  LOG(LS_INFO) << "Connecting tunnel";
+  // This will try to connect the PseudoTcpChannel. If and when that
+  // succeeds, then ssl negotiation will take place, and when that
+  // succeeds, the tunnel stream will finally open.
+  VERIFY(channel_->Connect(
+      content_name, "tcp", ICE_CANDIDATE_COMPONENT_DEFAULT));
+}
+
+}  // namespace cricket
diff --git a/libjingle/tunnel/securetunnelsessionclient.h b/libjingle/tunnel/securetunnelsessionclient.h
new file mode 100644
index 0000000..414aaf5
--- /dev/null
+++ b/libjingle/tunnel/securetunnelsessionclient.h
@@ -0,0 +1,165 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, 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.
+ */
+
+// SecureTunnelSessionClient and SecureTunnelSession.
+// SecureTunnelSessionClient extends TunnelSessionClient to exchange
+// certificates as part of the session description.
+// SecureTunnelSession is a TunnelSession that wraps the underlying
+// tunnel stream into an SSLStreamAdapter.
+
+#ifndef TALK_SESSION_TUNNEL_SECURETUNNELSESSIONCLIENT_H_
+#define TALK_SESSION_TUNNEL_SECURETUNNELSESSIONCLIENT_H_
+
+#include <string>
+
+#include "webrtc/libjingle/tunnel/tunnelsessionclient.h"
+#include "webrtc/base/sslidentity.h"
+#include "webrtc/base/sslstreamadapter.h"
+
+namespace cricket {
+
+class SecureTunnelSession;  // below
+
+// SecureTunnelSessionClient
+
+// This TunnelSessionClient establishes secure tunnels protected by
+// SSL/TLS. The PseudoTcpChannel stream is wrapped with an
+// SSLStreamAdapter. An SSLIdentity must be set or generated.
+//
+// The TunnelContentDescription is extended to include the client and
+// server certificates. The initiator acts as the client. The session
+// initiate stanza carries a description that contains the client's
+// certificate, and the session accept response's description has the
+// server certificate added to it.
+
+class SecureTunnelSessionClient : public TunnelSessionClient {
+ public:
+  // The jid is used as the name for sessions for outgoing tunnels.
+  // manager is the SessionManager to which we register this client
+  // and its sessions.
+  SecureTunnelSessionClient(const buzz::Jid& jid, SessionManager* manager);
+
+  // Configures this client to use a preexisting SSLIdentity.
+  // The client takes ownership of the identity object.
+  // Use either SetIdentity or GenerateIdentity, and only once.
+  void SetIdentity(rtc::SSLIdentity* identity);
+
+  // Generates an identity from nothing.
+  // Returns true if generation was successful.
+  // Use either SetIdentity or GenerateIdentity, and only once.
+  bool GenerateIdentity();
+
+  // Returns our identity for SSL purposes, as either set by
+  // SetIdentity() or generated by GenerateIdentity(). Call this
+  // method only after our identity has been successfully established
+  // by one of those methods.
+  rtc::SSLIdentity& GetIdentity() const;
+
+  // Inherited methods
+  virtual void OnIncomingTunnel(const buzz::Jid& jid, Session *session);
+  virtual bool ParseContent(SignalingProtocol protocol,
+                            const buzz::XmlElement* elem,
+                            ContentDescription** content,
+                            ParseError* error);
+  virtual bool WriteContent(SignalingProtocol protocol,
+                            const ContentDescription* content,
+                            buzz::XmlElement** elem,
+                            WriteError* error);
+  virtual SessionDescription* CreateOffer(
+      const buzz::Jid &jid, const std::string &description);
+  virtual SessionDescription* CreateAnswer(
+      const SessionDescription* offer);
+
+ protected:
+  virtual TunnelSession* MakeTunnelSession(
+      Session* session, rtc::Thread* stream_thread,
+      TunnelSessionRole role);
+
+ private:
+  // Our identity (key and certificate) for SSL purposes. The
+  // certificate part will be communicated within the session
+  // description. The identity will be passed to the SSLStreamAdapter
+  // and used for SSL authentication.
+  rtc::scoped_ptr<rtc::SSLIdentity> identity_;
+
+  DISALLOW_EVIL_CONSTRUCTORS(SecureTunnelSessionClient);
+};
+
+// SecureTunnelSession:
+// A TunnelSession represents one session for one client. It
+// provides the actual tunnel stream and handles state changes.
+// A SecureTunnelSession is a TunnelSession that wraps the underlying
+// tunnel stream into an SSLStreamAdapter.
+
+class SecureTunnelSession : public TunnelSession {
+ public:
+  // This TunnelSession will tie together the given client and session.
+  // stream_thread is passed to the PseudoTCPChannel: it's the thread
+  // designated to interact with the tunnel stream.
+  // role is either INITIATOR or RESPONDER, depending on who is
+  // initiating the session.
+  SecureTunnelSession(SecureTunnelSessionClient* client, Session* session,
+                      rtc::Thread* stream_thread,
+                      TunnelSessionRole role);
+
+  // Returns the stream that implements the actual P2P tunnel.
+  // This may be called only once. Caller is responsible for freeing
+  // the returned object.
+  virtual rtc::StreamInterface* GetStream();
+
+ protected:
+  // Inherited method: callback on accepting a session.
+  virtual void OnAccept();
+
+  // Helper method for GetStream() that Instantiates the
+  // SSLStreamAdapter to wrap the PseudoTcpChannel's stream, and
+  // configures it with our identity and role.
+  rtc::StreamInterface* MakeSecureStream(
+      rtc::StreamInterface* stream);
+
+  // Our role in requesting the tunnel: INITIATOR or
+  // RESPONDER. Translates to our role in SSL negotiation:
+  // respectively client or server. Also indicates which slot of the
+  // SecureTunnelContentDescription our cert goes into: client-cert or
+  // server-cert respectively.
+  TunnelSessionRole role_;
+
+  // This is the stream representing the usable tunnel endpoint.  It's
+  // a StreamReference wrapping the SSLStreamAdapter instance, which
+  // further wraps a PseudoTcpChannel::InternalStream. The
+  // StreamReference is because in the case of CreateTunnel(), the
+  // stream endpoint is returned early, but we need to keep a handle
+  // on it so we can setup the peer certificate when we receive it
+  // later.
+  rtc::scoped_ptr<rtc::StreamReference> ssl_stream_reference_;
+
+  DISALLOW_EVIL_CONSTRUCTORS(SecureTunnelSession);
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_TUNNEL_SECURETUNNELSESSIONCLIENT_H_
diff --git a/libjingle/tunnel/tunnelsessionclient.cc b/libjingle/tunnel/tunnelsessionclient.cc
new file mode 100644
index 0000000..7d2a7d1
--- /dev/null
+++ b/libjingle/tunnel/tunnelsessionclient.cc
@@ -0,0 +1,432 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, 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 "pseudotcpchannel.h"
+#include "webrtc/p2p/base/constants.h"
+#include "webrtc/p2p/base/transportchannel.h"
+#include "webrtc/libjingle/xmllite/xmlelement.h"
+#include "tunnelsessionclient.h"
+#include "webrtc/base/basicdefs.h"
+#include "webrtc/base/basictypes.h"
+#include "webrtc/base/common.h"
+#include "webrtc/base/helpers.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/stringutils.h"
+
+namespace cricket {
+
+const char NS_TUNNEL[] = "http://www.google.com/talk/tunnel";
+const buzz::StaticQName QN_TUNNEL_DESCRIPTION = { NS_TUNNEL, "description" };
+const buzz::StaticQName QN_TUNNEL_TYPE = { NS_TUNNEL, "type" };
+const char CN_TUNNEL[] = "tunnel";
+
+enum {
+  MSG_CLOCK = 1,
+  MSG_DESTROY,
+  MSG_TERMINATE,
+  MSG_EVENT,
+  MSG_CREATE_TUNNEL,
+};
+
+struct EventData : public rtc::MessageData {
+  int event, error;
+  EventData(int ev, int err = 0) : event(ev), error(err) { }
+};
+
+struct CreateTunnelData : public rtc::MessageData {
+  buzz::Jid jid;
+  std::string description;
+  rtc::Thread* thread;
+  rtc::StreamInterface* stream;
+};
+
+extern const rtc::ConstantLabel SESSION_STATES[];
+
+const rtc::ConstantLabel SESSION_STATES[] = {
+  KLABEL(Session::STATE_INIT),
+  KLABEL(Session::STATE_SENTINITIATE),
+  KLABEL(Session::STATE_RECEIVEDINITIATE),
+  KLABEL(Session::STATE_SENTACCEPT),
+  KLABEL(Session::STATE_RECEIVEDACCEPT),
+  KLABEL(Session::STATE_SENTMODIFY),
+  KLABEL(Session::STATE_RECEIVEDMODIFY),
+  KLABEL(Session::STATE_SENTREJECT),
+  KLABEL(Session::STATE_RECEIVEDREJECT),
+  KLABEL(Session::STATE_SENTREDIRECT),
+  KLABEL(Session::STATE_SENTTERMINATE),
+  KLABEL(Session::STATE_RECEIVEDTERMINATE),
+  KLABEL(Session::STATE_INPROGRESS),
+  KLABEL(Session::STATE_DEINIT),
+  LASTLABEL
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// TunnelContentDescription
+///////////////////////////////////////////////////////////////////////////////
+
+struct TunnelContentDescription : public ContentDescription {
+  std::string description;
+
+  TunnelContentDescription(const std::string& desc) : description(desc) { }
+  virtual ContentDescription* Copy() const {
+    return new TunnelContentDescription(*this);
+  }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// TunnelSessionClientBase
+///////////////////////////////////////////////////////////////////////////////
+
+TunnelSessionClientBase::TunnelSessionClientBase(const buzz::Jid& jid,
+                                SessionManager* manager, const std::string &ns)
+  : jid_(jid), session_manager_(manager), namespace_(ns), shutdown_(false) {
+  session_manager_->AddClient(namespace_, this);
+}
+
+TunnelSessionClientBase::~TunnelSessionClientBase() {
+  shutdown_ = true;
+  for (std::vector<TunnelSession*>::iterator it = sessions_.begin();
+       it != sessions_.end();
+       ++it) {
+     Session* session = (*it)->ReleaseSession(true);
+     session_manager_->DestroySession(session);
+  }
+  session_manager_->RemoveClient(namespace_);
+}
+
+void TunnelSessionClientBase::OnSessionCreate(Session* session, bool received) {
+  LOG(LS_INFO) << "TunnelSessionClientBase::OnSessionCreate: received=" 
+               << received;
+  ASSERT(session_manager_->signaling_thread()->IsCurrent());
+  if (received)
+    sessions_.push_back(
+        MakeTunnelSession(session, rtc::Thread::Current(), RESPONDER));
+}
+
+void TunnelSessionClientBase::OnSessionDestroy(Session* session) {
+  LOG(LS_INFO) << "TunnelSessionClientBase::OnSessionDestroy";
+  ASSERT(session_manager_->signaling_thread()->IsCurrent());
+  if (shutdown_)
+    return;
+  for (std::vector<TunnelSession*>::iterator it = sessions_.begin();
+       it != sessions_.end();
+       ++it) {
+    if ((*it)->HasSession(session)) {
+      VERIFY((*it)->ReleaseSession(false) == session);
+      sessions_.erase(it);
+      return;
+    }
+  }
+}
+
+rtc::StreamInterface* TunnelSessionClientBase::CreateTunnel(
+    const buzz::Jid& to, const std::string& description) {
+  // Valid from any thread
+  CreateTunnelData data;
+  data.jid = to;
+  data.description = description;
+  data.thread = rtc::Thread::Current();
+  data.stream = NULL;
+  session_manager_->signaling_thread()->Send(this, MSG_CREATE_TUNNEL, &data);
+  return data.stream;
+}
+
+rtc::StreamInterface* TunnelSessionClientBase::AcceptTunnel(
+    Session* session) {
+  ASSERT(session_manager_->signaling_thread()->IsCurrent());
+  TunnelSession* tunnel = NULL;
+  for (std::vector<TunnelSession*>::iterator it = sessions_.begin();
+       it != sessions_.end();
+       ++it) {
+    if ((*it)->HasSession(session)) {
+      tunnel = *it;
+      break;
+    }
+  }
+  ASSERT(tunnel != NULL);
+
+  SessionDescription* answer = CreateAnswer(session->remote_description());
+  if (answer == NULL)
+    return NULL;
+
+  session->Accept(answer);
+  return tunnel->GetStream();
+}
+
+void TunnelSessionClientBase::DeclineTunnel(Session* session) {
+  ASSERT(session_manager_->signaling_thread()->IsCurrent());
+  session->Reject(STR_TERMINATE_DECLINE);
+}
+
+void TunnelSessionClientBase::OnMessage(rtc::Message* pmsg) {
+  if (pmsg->message_id == MSG_CREATE_TUNNEL) {
+    ASSERT(session_manager_->signaling_thread()->IsCurrent());
+    CreateTunnelData* data = static_cast<CreateTunnelData*>(pmsg->pdata);
+    SessionDescription* offer = CreateOffer(data->jid, data->description);
+    if (offer == NULL) {
+      return;
+    }
+
+    Session* session = session_manager_->CreateSession(jid_.Str(), namespace_);
+    TunnelSession* tunnel = MakeTunnelSession(session, data->thread,
+                                              INITIATOR);
+    sessions_.push_back(tunnel);
+    session->Initiate(data->jid.Str(), offer);
+    data->stream = tunnel->GetStream();
+  }
+}
+
+TunnelSession* TunnelSessionClientBase::MakeTunnelSession(
+    Session* session, rtc::Thread* stream_thread,
+    TunnelSessionRole /*role*/) {
+  return new TunnelSession(this, session, stream_thread);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// TunnelSessionClient
+///////////////////////////////////////////////////////////////////////////////
+
+TunnelSessionClient::TunnelSessionClient(const buzz::Jid& jid,
+                                         SessionManager* manager,
+                                         const std::string &ns)
+    : TunnelSessionClientBase(jid, manager, ns) {
+}
+
+TunnelSessionClient::TunnelSessionClient(const buzz::Jid& jid,
+                                         SessionManager* manager)
+    : TunnelSessionClientBase(jid, manager, NS_TUNNEL) {
+}
+
+TunnelSessionClient::~TunnelSessionClient() {
+}
+
+
+bool TunnelSessionClient::ParseContent(SignalingProtocol protocol,
+                                       const buzz::XmlElement* elem,
+                                       ContentDescription** content,
+                                       ParseError* error) {
+  if (const buzz::XmlElement* type_elem = elem->FirstNamed(QN_TUNNEL_TYPE)) {
+    *content = new TunnelContentDescription(type_elem->BodyText());
+    return true;
+  }
+  return false;
+}
+
+bool TunnelSessionClient::WriteContent(
+    SignalingProtocol protocol,
+    const ContentDescription* untyped_content,
+    buzz::XmlElement** elem, WriteError* error) {
+  const TunnelContentDescription* content =
+      static_cast<const TunnelContentDescription*>(untyped_content);
+
+  buzz::XmlElement* root = new buzz::XmlElement(QN_TUNNEL_DESCRIPTION, true);
+  buzz::XmlElement* type_elem = new buzz::XmlElement(QN_TUNNEL_TYPE);
+  type_elem->SetBodyText(content->description);
+  root->AddElement(type_elem);
+  *elem = root;
+  return true;
+}
+
+SessionDescription* NewTunnelSessionDescription(
+    const std::string& content_name, ContentDescription* content) {
+  SessionDescription* sdesc = new SessionDescription();
+  sdesc->AddContent(content_name, NS_TUNNEL, content);
+  return sdesc;
+}
+
+bool FindTunnelContent(const cricket::SessionDescription* sdesc,
+                       std::string* name,
+                       const TunnelContentDescription** content) {
+  const ContentInfo* cinfo = sdesc->FirstContentByType(NS_TUNNEL);
+  if (cinfo == NULL)
+    return false;
+
+  *name = cinfo->name;
+  *content = static_cast<const TunnelContentDescription*>(
+      cinfo->description);
+  return true;
+}
+
+void TunnelSessionClient::OnIncomingTunnel(const buzz::Jid &jid,
+                                           Session *session) {
+  std::string content_name;
+  const TunnelContentDescription* content = NULL;
+  if (!FindTunnelContent(session->remote_description(),
+                         &content_name, &content)) {
+    session->Reject(STR_TERMINATE_INCOMPATIBLE_PARAMETERS);
+    return;
+  }
+
+  SignalIncomingTunnel(this, jid, content->description, session);
+}
+
+SessionDescription* TunnelSessionClient::CreateOffer(
+    const buzz::Jid &jid, const std::string &description) {
+  SessionDescription* offer = NewTunnelSessionDescription(
+      CN_TUNNEL, new TunnelContentDescription(description));
+  rtc::scoped_ptr<TransportDescription> tdesc(
+      session_manager_->transport_desc_factory()->CreateOffer(
+          TransportOptions(), NULL));
+  if (tdesc.get()) {
+    offer->AddTransportInfo(TransportInfo(CN_TUNNEL, *tdesc));
+  } else {
+    delete offer;
+    offer = NULL;
+  }
+  return offer;
+}
+
+SessionDescription* TunnelSessionClient::CreateAnswer(
+    const SessionDescription* offer) {
+  std::string content_name;
+  const TunnelContentDescription* offer_tunnel = NULL;
+  if (!FindTunnelContent(offer, &content_name, &offer_tunnel))
+    return NULL;
+
+  SessionDescription* answer = NewTunnelSessionDescription(
+      content_name, new TunnelContentDescription(offer_tunnel->description));
+  const TransportInfo* tinfo = offer->GetTransportInfoByName(content_name);
+  if (tinfo) {
+    const TransportDescription* offer_tdesc = &tinfo->description;
+    ASSERT(offer_tdesc != NULL);
+    rtc::scoped_ptr<TransportDescription> tdesc(
+      session_manager_->transport_desc_factory()->CreateAnswer(
+          offer_tdesc, TransportOptions(),  NULL));
+    if (tdesc.get()) {
+      answer->AddTransportInfo(TransportInfo(content_name, *tdesc));
+    } else {
+      delete answer;
+      answer = NULL;
+    }
+  }
+  return answer;
+}
+///////////////////////////////////////////////////////////////////////////////
+// TunnelSession
+///////////////////////////////////////////////////////////////////////////////
+
+//
+// Signalling thread methods
+//
+
+TunnelSession::TunnelSession(TunnelSessionClientBase* client, Session* session,
+                             rtc::Thread* stream_thread)
+    : client_(client), session_(session), channel_(NULL) {
+  ASSERT(client_ != NULL);
+  ASSERT(session_ != NULL);
+  session_->SignalState.connect(this, &TunnelSession::OnSessionState);
+  channel_ = new PseudoTcpChannel(stream_thread, session_);
+  channel_->SignalChannelClosed.connect(this, &TunnelSession::OnChannelClosed);
+}
+
+TunnelSession::~TunnelSession() {
+  ASSERT(client_ != NULL);
+  ASSERT(session_ == NULL);
+  ASSERT(channel_ == NULL);
+}
+
+rtc::StreamInterface* TunnelSession::GetStream() {
+  ASSERT(channel_ != NULL);
+  return channel_->GetStream();
+}
+
+bool TunnelSession::HasSession(Session* session) {
+  ASSERT(NULL != session_);
+  return (session_ == session);
+}
+
+Session* TunnelSession::ReleaseSession(bool channel_exists) {
+  ASSERT(NULL != session_);
+  ASSERT(NULL != channel_);
+  Session* session = session_;
+  session_->SignalState.disconnect(this);
+  session_ = NULL;
+  if (channel_exists)
+    channel_->SignalChannelClosed.disconnect(this);
+  channel_ = NULL;
+  delete this;
+  return session;
+}
+
+void TunnelSession::OnSessionState(BaseSession* session,
+                                   BaseSession::State state) {
+  LOG(LS_INFO) << "TunnelSession::OnSessionState("
+               << rtc::nonnull(
+                    rtc::FindLabel(state, SESSION_STATES), "Unknown")
+               << ")";
+  ASSERT(session == session_);
+
+  switch (state) {
+  case Session::STATE_RECEIVEDINITIATE:
+    OnInitiate();
+    break;
+  case Session::STATE_SENTACCEPT:
+  case Session::STATE_RECEIVEDACCEPT:
+    OnAccept();
+    break;
+  case Session::STATE_SENTTERMINATE:
+  case Session::STATE_RECEIVEDTERMINATE:
+    OnTerminate();
+    break;
+  case Session::STATE_DEINIT:
+    // ReleaseSession should have been called before this.
+    ASSERT(false);
+    break;
+  default:
+    break;
+  }
+}
+
+void TunnelSession::OnInitiate() {
+  ASSERT(client_ != NULL);
+  ASSERT(session_ != NULL);
+  client_->OnIncomingTunnel(buzz::Jid(session_->remote_name()), session_);
+}
+
+void TunnelSession::OnAccept() {
+  ASSERT(channel_ != NULL);
+  const ContentInfo* content =
+      session_->remote_description()->FirstContentByType(NS_TUNNEL);
+  ASSERT(content != NULL);
+  VERIFY(channel_->Connect(
+      content->name, "tcp", ICE_CANDIDATE_COMPONENT_DEFAULT));
+}
+
+void TunnelSession::OnTerminate() {
+  ASSERT(channel_ != NULL);
+  channel_->OnSessionTerminate(session_);
+}
+
+void TunnelSession::OnChannelClosed(PseudoTcpChannel* channel) {
+  ASSERT(channel_ == channel);
+  ASSERT(session_ != NULL);
+  session_->Terminate();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace cricket
diff --git a/libjingle/tunnel/tunnelsessionclient.h b/libjingle/tunnel/tunnelsessionclient.h
new file mode 100644
index 0000000..990d8bf
--- /dev/null
+++ b/libjingle/tunnel/tunnelsessionclient.h
@@ -0,0 +1,182 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, 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.
+ */
+
+#ifndef __TUNNELSESSIONCLIENT_H__
+#define __TUNNELSESSIONCLIENT_H__
+
+#include <vector>
+
+#include "webrtc/p2p/base/constants.h"
+#include "webrtc/libjingle/tunnel/pseudotcp.h"
+#include "webrtc/p2p/base/session.h"
+#include "webrtc/libjingle/session/sessionclient.h"
+#include "webrtc/p2p/base/sessiondescription.h"
+#include "webrtc/libjingle/session/sessionmanager.h"
+#include "webrtc/libjingle/xmllite/qname.h"
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/base/criticalsection.h"
+#include "webrtc/base/stream.h"
+
+namespace cricket {
+
+class TunnelSession;
+class TunnelStream;
+
+enum TunnelSessionRole { INITIATOR, RESPONDER };
+
+///////////////////////////////////////////////////////////////////////////////
+// TunnelSessionClient
+///////////////////////////////////////////////////////////////////////////////
+
+// Base class is still abstract
+class TunnelSessionClientBase
+  : public SessionClient, public rtc::MessageHandler {
+public:
+  TunnelSessionClientBase(const buzz::Jid& jid, SessionManager* manager,
+                          const std::string &ns);
+  virtual ~TunnelSessionClientBase();
+
+  const buzz::Jid& jid() const { return jid_; }
+  SessionManager* session_manager() const { return session_manager_; }
+
+  void OnSessionCreate(Session* session, bool received);
+  void OnSessionDestroy(Session* session);
+
+  // This can be called on any thread.  The stream interface is
+  // thread-safe, but notifications must be registered on the creating
+  // thread.
+  rtc::StreamInterface* CreateTunnel(const buzz::Jid& to,
+                                           const std::string& description);
+
+  rtc::StreamInterface* AcceptTunnel(Session* session);
+  void DeclineTunnel(Session* session);
+
+  // Invoked on an incoming tunnel
+  virtual void OnIncomingTunnel(const buzz::Jid &jid, Session *session) = 0;
+
+  // Invoked on an outgoing session request
+  virtual SessionDescription* CreateOffer(
+      const buzz::Jid &jid, const std::string &description) = 0;
+  // Invoked on a session request accept to create
+  // the local-side session description
+  virtual SessionDescription* CreateAnswer(
+      const SessionDescription* offer) = 0;
+
+protected:
+
+  void OnMessage(rtc::Message* pmsg);
+
+  // helper method to instantiate TunnelSession. By overriding this,
+  // subclasses of TunnelSessionClient are able to instantiate
+  // subclasses of TunnelSession instead.
+  virtual TunnelSession* MakeTunnelSession(Session* session,
+                                           rtc::Thread* stream_thread,
+                                           TunnelSessionRole role);
+
+  buzz::Jid jid_;
+  SessionManager* session_manager_;
+  std::vector<TunnelSession*> sessions_;
+  std::string namespace_;
+  bool shutdown_;
+};
+
+class TunnelSessionClient
+  : public TunnelSessionClientBase, public sigslot::has_slots<>  {
+public:
+  TunnelSessionClient(const buzz::Jid& jid, SessionManager* manager);
+  TunnelSessionClient(const buzz::Jid& jid, SessionManager* manager,
+                      const std::string &ns);
+  virtual ~TunnelSessionClient();
+
+  virtual bool ParseContent(SignalingProtocol protocol,
+                            const buzz::XmlElement* elem,
+                            ContentDescription** content,
+                            ParseError* error);
+  virtual bool WriteContent(SignalingProtocol protocol,
+                            const ContentDescription* content,
+                            buzz::XmlElement** elem,
+                            WriteError* error);
+
+  // Signal arguments are this, initiator, description, session
+  sigslot::signal4<TunnelSessionClient*, buzz::Jid, std::string, Session*>
+    SignalIncomingTunnel;
+
+  virtual void OnIncomingTunnel(const buzz::Jid &jid,
+                                Session *session);
+  virtual SessionDescription* CreateOffer(
+      const buzz::Jid &jid, const std::string &description);
+  virtual SessionDescription* CreateAnswer(
+      const SessionDescription* offer);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// TunnelSession
+// Note: The lifetime of TunnelSession is complicated.  It needs to survive
+// until the following three conditions are true:
+// 1) TunnelStream has called Close (tracked via non-null stream_)
+// 2) PseudoTcp has completed (tracked via non-null tcp_)
+// 3) Session has been destroyed (tracked via non-null session_)
+// This is accomplished by calling CheckDestroy after these indicators change.
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+// TunnelStream
+// Note: Because TunnelStream provides a stream interface, its lifetime is
+// controlled by the owner of the stream pointer.  As a result, we must support
+// both the TunnelSession disappearing before TunnelStream, and vice versa.
+///////////////////////////////////////////////////////////////////////////////
+
+class PseudoTcpChannel;
+
+class TunnelSession : public sigslot::has_slots<> {
+ public:
+  // Signalling thread methods
+  TunnelSession(TunnelSessionClientBase* client, Session* session,
+                rtc::Thread* stream_thread);
+
+  virtual rtc::StreamInterface* GetStream();
+  bool HasSession(Session* session);
+  Session* ReleaseSession(bool channel_exists);
+
+ protected:
+  virtual ~TunnelSession();
+
+  virtual void OnSessionState(BaseSession* session, BaseSession::State state);
+  virtual void OnInitiate();
+  virtual void OnAccept();
+  virtual void OnTerminate();
+  virtual void OnChannelClosed(PseudoTcpChannel* channel);
+
+  TunnelSessionClientBase* client_;
+  Session* session_;
+  PseudoTcpChannel* channel_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace cricket
+
+#endif // __TUNNELSESSIONCLIENT_H__
diff --git a/libjingle/tunnel/tunnelsessionclient_unittest.cc b/libjingle/tunnel/tunnelsessionclient_unittest.cc
new file mode 100644
index 0000000..f646223
--- /dev/null
+++ b/libjingle/tunnel/tunnelsessionclient_unittest.cc
@@ -0,0 +1,226 @@
+/*
+ * libjingle
+ * Copyright 2010, 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 "webrtc/libjingle/session/sessionmanager.h"
+#include "webrtc/p2p/base/transport.h"
+#include "webrtc/p2p/client/fakeportallocator.h"
+#include "webrtc/libjingle/tunnel/tunnelsessionclient.h"
+#include "webrtc/base/gunit.h"
+#include "webrtc/base/messagehandler.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/base/stream.h"
+#include "webrtc/base/thread.h"
+#include "webrtc/base/timeutils.h"
+
+static const int kTimeoutMs = 10000;
+static const int kBlockSize = 4096;
+static const buzz::Jid kLocalJid("local@localhost");
+static const buzz::Jid kRemoteJid("remote@localhost");
+
+// This test fixture creates the necessary plumbing to create and run
+// two TunnelSessionClients that talk to each other.
+class TunnelSessionClientTest : public testing::Test,
+                                public rtc::MessageHandler,
+                                public sigslot::has_slots<> {
+ public:
+  TunnelSessionClientTest()
+      : local_pa_(rtc::Thread::Current(), NULL),
+        remote_pa_(rtc::Thread::Current(), NULL),
+        local_sm_(&local_pa_, rtc::Thread::Current()),
+        remote_sm_(&remote_pa_, rtc::Thread::Current()),
+        local_client_(kLocalJid, &local_sm_),
+        remote_client_(kRemoteJid, &remote_sm_),
+        done_(false) {
+    local_sm_.SignalRequestSignaling.connect(this,
+        &TunnelSessionClientTest::OnLocalRequestSignaling);
+    local_sm_.SignalOutgoingMessage.connect(this,
+        &TunnelSessionClientTest::OnOutgoingMessage);
+    remote_sm_.SignalRequestSignaling.connect(this,
+        &TunnelSessionClientTest::OnRemoteRequestSignaling);
+    remote_sm_.SignalOutgoingMessage.connect(this,
+        &TunnelSessionClientTest::OnOutgoingMessage);
+    remote_client_.SignalIncomingTunnel.connect(this,
+        &TunnelSessionClientTest::OnIncomingTunnel);
+  }
+
+  // Transfer the desired amount of data from the local to the remote client.
+  void TestTransfer(int size) {
+    // Create some dummy data to send.
+    send_stream_.ReserveSize(size);
+    for (int i = 0; i < size; ++i) {
+      char ch = static_cast<char>(i);
+      send_stream_.Write(&ch, 1, NULL, NULL);
+    }
+    send_stream_.Rewind();
+    // Prepare the receive stream.
+    recv_stream_.ReserveSize(size);
+    // Create the tunnel and set things in motion.
+    local_tunnel_.reset(local_client_.CreateTunnel(kRemoteJid, "test"));
+    local_tunnel_->SignalEvent.connect(this,
+        &TunnelSessionClientTest::OnStreamEvent);
+    EXPECT_TRUE_WAIT(done_, kTimeoutMs);
+    // Make sure we received the right data.
+    EXPECT_EQ(0, memcmp(send_stream_.GetBuffer(),
+                        recv_stream_.GetBuffer(), size));
+  }
+
+ private:
+  enum { MSG_LSIGNAL, MSG_RSIGNAL };
+
+  // There's no SessionManager* argument in this callback, so we need 2 of them.
+  void OnLocalRequestSignaling() {
+    local_sm_.OnSignalingReady();
+  }
+  void OnRemoteRequestSignaling() {
+    remote_sm_.OnSignalingReady();
+  }
+
+  // Post a message, to avoid problems with directly connecting the callbacks.
+  void OnOutgoingMessage(cricket::SessionManager* manager,
+                         const buzz::XmlElement* stanza) {
+    if (manager == &local_sm_) {
+      rtc::Thread::Current()->Post(this, MSG_LSIGNAL,
+          rtc::WrapMessageData(*stanza));
+    } else if (manager == &remote_sm_) {
+      rtc::Thread::Current()->Post(this, MSG_RSIGNAL,
+          rtc::WrapMessageData(*stanza));
+    }
+  }
+
+  // Need to add a "from=" attribute (normally added by the server)
+  // Then route the incoming signaling message to the "other" session manager.
+  virtual void OnMessage(rtc::Message* message) {
+    rtc::TypedMessageData<buzz::XmlElement>* data =
+        static_cast<rtc::TypedMessageData<buzz::XmlElement>*>(
+            message->pdata);
+    bool response = data->data().Attr(buzz::QN_TYPE) == buzz::STR_RESULT;
+    if (message->message_id == MSG_RSIGNAL) {
+      data->data().AddAttr(buzz::QN_FROM, remote_client_.jid().Str());
+      if (!response) {
+        local_sm_.OnIncomingMessage(&data->data());
+      } else {
+        local_sm_.OnIncomingResponse(NULL, &data->data());
+      }
+    } else if (message->message_id == MSG_LSIGNAL) {
+      data->data().AddAttr(buzz::QN_FROM, local_client_.jid().Str());
+      if (!response) {
+        remote_sm_.OnIncomingMessage(&data->data());
+      } else {
+        remote_sm_.OnIncomingResponse(NULL, &data->data());
+      }
+    }
+    delete data;
+  }
+
+  // Accept the tunnel when it arrives and wire up the stream.
+  void OnIncomingTunnel(cricket::TunnelSessionClient* client,
+                        buzz::Jid jid, std::string description,
+                        cricket::Session* session) {
+    remote_tunnel_.reset(remote_client_.AcceptTunnel(session));
+    remote_tunnel_->SignalEvent.connect(this,
+        &TunnelSessionClientTest::OnStreamEvent);
+  }
+
+  // Send from send_stream_ as long as we're not flow-controlled.
+  // Read bytes out into recv_stream_ as they arrive.
+  // End the test when we are notified that the local side has closed the
+  // tunnel. All data has been read out at this point.
+  void OnStreamEvent(rtc::StreamInterface* stream, int events,
+                     int error) {
+    if (events & rtc::SE_READ) {
+      if (stream == remote_tunnel_.get()) {
+        ReadData();
+      }
+    }
+    if (events & rtc::SE_WRITE) {
+      if (stream == local_tunnel_.get()) {
+        bool done = false;
+        WriteData(&done);
+        if (done) {
+          local_tunnel_->Close();
+        }
+      }
+    }
+    if (events & rtc::SE_CLOSE) {
+      if (stream == remote_tunnel_.get()) {
+        remote_tunnel_->Close();
+        done_ = true;
+      }
+    }
+  }
+
+  // Spool from the tunnel into recv_stream.
+  // Flow() doesn't work here because it won't write if the read blocks.
+  void ReadData() {
+    char block[kBlockSize];
+    size_t read, position;
+    rtc::StreamResult res;
+    while ((res = remote_tunnel_->Read(block, sizeof(block), &read, NULL)) ==
+        rtc::SR_SUCCESS) {
+      recv_stream_.Write(block, read, NULL, NULL);
+    }
+    ASSERT(res != rtc::SR_EOS);
+    recv_stream_.GetPosition(&position);
+    LOG(LS_VERBOSE) << "Recv position: " << position;
+  }
+  // Spool from send_stream into the tunnel. Back up if we get flow controlled.
+  void WriteData(bool* done) {
+    char block[kBlockSize];
+    size_t leftover = 0, position;
+    rtc::StreamResult res = rtc::Flow(&send_stream_,
+        block, sizeof(block), local_tunnel_.get(), &leftover);
+    if (res == rtc::SR_BLOCK) {
+      send_stream_.GetPosition(&position);
+      send_stream_.SetPosition(position - leftover);
+      LOG(LS_VERBOSE) << "Send position: " << position - leftover;
+      *done = false;
+    } else if (res == rtc::SR_SUCCESS) {
+      *done = true;
+    } else {
+      ASSERT(false);  // shouldn't happen
+    }
+  }
+
+ private:
+  cricket::FakePortAllocator local_pa_;
+  cricket::FakePortAllocator remote_pa_;
+  cricket::SessionManager local_sm_;
+  cricket::SessionManager remote_sm_;
+  cricket::TunnelSessionClient local_client_;
+  cricket::TunnelSessionClient remote_client_;
+  rtc::scoped_ptr<rtc::StreamInterface> local_tunnel_;
+  rtc::scoped_ptr<rtc::StreamInterface> remote_tunnel_;
+  rtc::MemoryStream send_stream_;
+  rtc::MemoryStream recv_stream_;
+  bool done_;
+};
+
+// Test the normal case of sending data from one side to the other.
+TEST_F(TunnelSessionClientTest, TestTransfer) {
+  TestTransfer(1000000);
+}
diff --git a/libjingle/xmllite/OWNERS b/libjingle/xmllite/OWNERS
deleted file mode 100644
index 1a24a6a..0000000
--- a/libjingle/xmllite/OWNERS
+++ /dev/null
@@ -1,13 +0,0 @@
-henrika@webrtc.org
-henrike@webrtc.org
-henrikg@webrtc.org
-hta@webrtc.org
-jiayl@webrtc.org
-juberti@webrtc.org
-mflodman@webrtc.org
-perkj@webrtc.org
-pthatcher@webrtc.org
-sergeyu@chromium.org
-tommi@webrtc.org
-
-per-file BUILD.gn=kjellander@webrtc.org
diff --git a/p2p/base/p2ptransport.cc b/p2p/base/p2ptransport.cc
index e873756..8a2c5a3 100644
--- a/p2p/base/p2ptransport.cc
+++ b/p2p/base/p2ptransport.cc
@@ -15,9 +15,9 @@
 
 #include "webrtc/p2p/base/constants.h"
 #include "webrtc/p2p/base/p2ptransportchannel.h"
-#include "webrtc/p2p/base/parsing.h"
-#include "webrtc/p2p/base/sessionmanager.h"
-#include "webrtc/p2p/base/sessionmessages.h"
+#include "webrtc/libjingle/session/parsing.h"
+#include "webrtc/libjingle/session/sessionmanager.h"
+#include "webrtc/libjingle/session/sessionmessages.h"
 #include "webrtc/libjingle/xmllite/qname.h"
 #include "webrtc/libjingle/xmllite/xmlelement.h"
 #include "webrtc/libjingle/xmpp/constants.h"
diff --git a/p2p/base/rawtransport.cc b/p2p/base/rawtransport.cc
index 374ed98..3d9d7a8 100644
--- a/p2p/base/rawtransport.cc
+++ b/p2p/base/rawtransport.cc
@@ -11,10 +11,10 @@
 #include <string>
 #include <vector>
 #include "webrtc/p2p/base/constants.h"
-#include "webrtc/p2p/base/parsing.h"
+#include "webrtc/libjingle/session/parsing.h"
 #include "webrtc/p2p/base/rawtransport.h"
 #include "webrtc/p2p/base/rawtransportchannel.h"
-#include "webrtc/p2p/base/sessionmanager.h"
+#include "webrtc/libjingle/session/sessionmanager.h"
 #include "webrtc/libjingle/xmllite/qname.h"
 #include "webrtc/libjingle/xmllite/xmlelement.h"
 #include "webrtc/libjingle/xmpp/constants.h"
diff --git a/p2p/base/rawtransportchannel.cc b/p2p/base/rawtransportchannel.cc
index 5779c6e..50d084b 100644
--- a/p2p/base/rawtransportchannel.cc
+++ b/p2p/base/rawtransportchannel.cc
@@ -17,7 +17,7 @@
 #include "webrtc/p2p/base/portinterface.h"
 #include "webrtc/p2p/base/rawtransport.h"
 #include "webrtc/p2p/base/relayport.h"
-#include "webrtc/p2p/base/sessionmanager.h"
+#include "webrtc/libjingle/session/sessionmanager.h"
 #include "webrtc/p2p/base/stunport.h"
 #include "webrtc/libjingle/xmllite/qname.h"
 #include "webrtc/libjingle/xmllite/xmlelement.h"
diff --git a/p2p/base/session.cc b/p2p/base/session.cc
index 9749b14..723edfe 100644
--- a/p2p/base/session.cc
+++ b/p2p/base/session.cc
@@ -12,7 +12,7 @@
 
 #include "webrtc/p2p/base/dtlstransport.h"
 #include "webrtc/p2p/base/p2ptransport.h"
-#include "webrtc/p2p/base/sessionclient.h"
+#include "webrtc/libjingle/session/sessionclient.h"
 #include "webrtc/p2p/base/transport.h"
 #include "webrtc/p2p/base/transportchannelproxy.h"
 #include "webrtc/p2p/base/transportinfo.h"
diff --git a/p2p/base/session.h b/p2p/base/session.h
index f5eaf41..660b9c1 100644
--- a/p2p/base/session.h
+++ b/p2p/base/session.h
@@ -16,11 +16,11 @@
 #include <string>
 #include <vector>
 
-#include "webrtc/p2p/base/parsing.h"
+#include "webrtc/libjingle/session/parsing.h"
 #include "webrtc/p2p/base/port.h"
-#include "webrtc/p2p/base/sessionclient.h"
-#include "webrtc/p2p/base/sessionmanager.h"
-#include "webrtc/p2p/base/sessionmessages.h"
+#include "webrtc/libjingle/session/sessionclient.h"
+#include "webrtc/libjingle/session/sessionmanager.h"
+#include "webrtc/libjingle/session/sessionmessages.h"
 #include "webrtc/p2p/base/transport.h"
 #include "webrtc/libjingle/xmllite/xmlelement.h"
 #include "webrtc/libjingle/xmpp/constants.h"
diff --git a/p2p/base/session_unittest.cc b/p2p/base/session_unittest.cc
index d6f94b2..81a97f4 100644
--- a/p2p/base/session_unittest.cc
+++ b/p2p/base/session_unittest.cc
@@ -17,13 +17,13 @@
 #include "webrtc/p2p/base/basicpacketsocketfactory.h"
 #include "webrtc/p2p/base/constants.h"
 #include "webrtc/p2p/base/p2ptransport.h"
-#include "webrtc/p2p/base/parsing.h"
+#include "webrtc/libjingle/session/parsing.h"
 #include "webrtc/p2p/base/portallocator.h"
 #include "webrtc/p2p/base/relayport.h"
 #include "webrtc/p2p/base/relayserver.h"
 #include "webrtc/p2p/base/session.h"
-#include "webrtc/p2p/base/sessionclient.h"
-#include "webrtc/p2p/base/sessionmanager.h"
+#include "webrtc/libjingle/session/sessionclient.h"
+#include "webrtc/libjingle/session/sessionmanager.h"
 #include "webrtc/p2p/base/stunport.h"
 #include "webrtc/p2p/base/stunserver.h"
 #include "webrtc/p2p/base/transportchannel.h"
diff --git a/p2p/base/transport.cc b/p2p/base/transport.cc
index 07b204c..12a2bb3 100644
--- a/p2p/base/transport.cc
+++ b/p2p/base/transport.cc
@@ -12,9 +12,9 @@
 
 #include "webrtc/p2p/base/candidate.h"
 #include "webrtc/p2p/base/constants.h"
-#include "webrtc/p2p/base/parsing.h"
+#include "webrtc/libjingle/session/parsing.h"
 #include "webrtc/p2p/base/port.h"
-#include "webrtc/p2p/base/sessionmanager.h"
+#include "webrtc/libjingle/session/sessionmanager.h"
 #include "webrtc/p2p/base/transportchannelimpl.h"
 #include "webrtc/libjingle/xmllite/xmlelement.h"
 #include "webrtc/libjingle/xmpp/constants.h"
diff --git a/p2p/base/transport_unittest.cc b/p2p/base/transport_unittest.cc
index e5d7aca..8aa8721 100644
--- a/p2p/base/transport_unittest.cc
+++ b/p2p/base/transport_unittest.cc
@@ -11,9 +11,9 @@
 #include "webrtc/p2p/base/constants.h"
 #include "webrtc/p2p/base/fakesession.h"
 #include "webrtc/p2p/base/p2ptransport.h"
-#include "webrtc/p2p/base/parsing.h"
+#include "webrtc/libjingle/session/parsing.h"
 #include "webrtc/p2p/base/rawtransport.h"
-#include "webrtc/p2p/base/sessionmessages.h"
+#include "webrtc/libjingle/session/sessionmessages.h"
 #include "webrtc/libjingle/xmllite/xmlelement.h"
 #include "webrtc/libjingle/xmpp/constants.h"
 #include "webrtc/base/fakesslidentity.h"
diff --git a/p2p/p2p.gyp b/p2p/p2p.gyp
index 102e75b..d9b925a 100644
--- a/p2p/p2p.gyp
+++ b/p2p/p2p.gyp
@@ -39,8 +39,6 @@
         'base/p2ptransportchannel.cc',
         'base/p2ptransportchannel.h',
         'base/packetsocketfactory.h',
-        'base/parsing.cc',
-        'base/parsing.h',
         'base/port.cc',
         'base/port.h',
         'base/portallocator.cc',
@@ -50,8 +48,6 @@
         'base/portinterface.h',
         'base/portproxy.cc',
         'base/portproxy.h',
-        'base/pseudotcp.cc',
-        'base/pseudotcp.h',
         'base/rawtransport.cc',
         'base/rawtransport.h',
         'base/rawtransportchannel.cc',
@@ -62,14 +58,9 @@
         'base/relayserver.h',
         'base/session.cc',
         'base/session.h',
-        'base/sessionclient.h',
         'base/sessiondescription.cc',
         'base/sessiondescription.h',
         'base/sessionid.h',
-        'base/sessionmanager.cc',
-        'base/sessionmanager.h',
-        'base/sessionmessages.cc',
-        'base/sessionmessages.h',
         'base/stun.cc',
         'base/stun.h',
         'base/stunport.cc',
@@ -104,10 +95,14 @@
         'client/connectivitychecker.h',
         'client/httpportallocator.cc',
         'client/httpportallocator.h',
-        'client/sessionmanagertask.h',
-        'client/sessionsendtask.h',
         'client/socketmonitor.cc',
         'client/socketmonitor.h',
+        '<(webrtc_root)/libjingle/session/parsing.h',
+        '<(webrtc_root)/libjingle/session/parsing.cc',
+        '<(webrtc_root)/libjingle/session/sessionmanager.h',
+        '<(webrtc_root)/libjingle/session/sessionmanager.cc',
+        '<(webrtc_root)/libjingle/session/sessionmessages.h',
+        '<(webrtc_root)/libjingle/session/sessionmessages.cc',
       ],
       'direct_dependent_settings': {
         'cflags_cc!': [
diff --git a/p2p/p2p_tests.gypi b/p2p/p2p_tests.gypi
index f9e6959..7b73f13 100644
--- a/p2p/p2p_tests.gypi
+++ b/p2p/p2p_tests.gypi
@@ -19,7 +19,6 @@
           'base/p2ptransportchannel_unittest.cc',
           'base/port_unittest.cc',
           'base/portallocatorsessionproxy_unittest.cc',
-          'base/pseudotcp_unittest.cc',
           'base/relayport_unittest.cc',
           'base/relayserver_unittest.cc',
           'base/session_unittest.cc',