PacketSocketFactory and other updates for the chrome-sandbox branch.

git-svn-id: http://libjingle.googlecode.com/svn/branches/chrome-sandbox@53 dd674b97-3498-5ee5-1854-bdd07cd0ff33
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..e491a9e
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1 @@
+Google Inc.
diff --git a/CHANGELOG b/CHANGELOG
new file mode 100644
index 0000000..e0c746e
--- /dev/null
+++ b/CHANGELOG
@@ -0,0 +1,53 @@
+Libjingle
+
+0.5.2 - Jan 11, 2010
+  - Fixed build on Windows 7 with VS 2010
+  - Fixed build on Windows x64
+  - Fixed build on Mac OSX
+  - Added option to examples/call to enable encryption
+  - Improved logging
+  - Bug fixes
+
+0.5.1 - Nov 2, 2010
+  - Added support for call encryption.
+  - Added addtional XEP-166 and XEP-167 features:
+    - Call redirection
+    - Candidates in session-accept or session-initiate
+  - Added support for bandwidth control.
+  - Added features in examples/call:
+    - bandwidth control on initiate or accept
+    - turn on/off SSL
+    - control signaling protocol
+    - send chat message
+
+0.5.0 - Sep 16, 2010
+  - Implemented Jingle protocols XEP-166 and XEP-167.
+  - Backward compatible with Google Talk Call Signaling protocol implemented
+    in previous versions.
+  - Builds on Windows, Linux, and Mac OS X with swtoolkit.
+  - Removed GipsLiteMediaEngine.
+  - Added video support.
+  - Added FileMediaEngine to support both voice and video via RTP dump.
+  - Many bug fixes.
+
+0.4.0 - Feb 01, 2007
+  - Updated protocol.
+  - Added relay server support.
+  - Added proxy detection support.
+  - Many other assorted changes.
+
+0.3.0 - Mar 16 2006
+  - New GipsLiteMediaEngine included to make calls using the GIPS
+    VoiceEngine Lite media componentry on Windows.
+
+0.2.0 - Jan 27 2006
+  - Windows build fixes with Visual Studio Express project files.
+  - Pseudo-TCP support provides TCP-like reliability over a P2PSocket
+  - TunnelSessionClient establishes sessions for reliably sending data
+    using Pseudo-TCP.
+  - A new pcp example application transfers files from one user to
+    another using TunnelSessionClient.
+  - TLS login support for both example applications.
+
+0.1.0 - Dec 15 2005
+  - Initial release.
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..d11f105
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,25 @@
+Copyright (c) 2004--2005, Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+    * 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.
+    * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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.
\ No newline at end of file
diff --git a/README b/README
new file mode 100644
index 0000000..f70b726
--- /dev/null
+++ b/README
@@ -0,0 +1,85 @@
+Libjingle
+
+1. Introduction
+
+Libjingle is a set of components provided by Google to implement Jingle
+protocols XEP-166 (http://xmpp.org/extensions/xep-0166.html) and XEP-167
+(http://xmpp.org/extensions/xep-0167.html). Libjingle is also backward
+compatible with Google Talk Call Signaling
+(http://code.google.com/apis/talk/call_signaling.html). This package will
+create several static libraries you may link to your projects as needed.
+
+-talk               - No source files in talk/, just these subdirectories
+|-base              - Contains basic low-level portable utility functions for
+|                     things like threads and sockets
+|-p2p               - The P2P stack
+  |-base            - Base p2p functionality
+  |-client          - Hooks to tie it into XMPP
+|-session           - Signaling
+  |-phone           - Signaling code specific to making phone calls
+    |-testdata      - Samples of RTP voice and video dump
+  |-tunnel          - Tunnel session and channel
+|-xmllite           - XML parser
+|-xmpp              - XMPP engine
+
+In addition, this package contains two examples in talk/examples which
+illustrate the basic concepts of how the provided classes work.
+
+2. How to Build
+
+Libjingle is built with swtoolkit (http://code.google.com/p/swtoolkit/), which
+is a set of extensions to the open-source SCons build tool (www.scons.org).
+  * First, install Python 2.4 or later from http://www.python.org/.
+    Please note that since swtoolkit only works with Python 2.x, you will
+    not be able to use Python 3.x.
+
+  * Second, install the stand alone scons-local package 2.0.0 or later from
+    http://www.scons.org/download.php and set an environment variable,
+    SCONS_DIR, to point to the directory containing SCons, for example,
+    /src/libjingle/scons-local/scons-local-2.0.0.final.0/.
+
+  * Third, install swtoolkit from http://code.google.com/p/swtoolkit/.
+
+  * Finally, Libjingle depends on two open-source projects, expat and srtp.
+    Download expat from http://sourceforge.net/projects/expat/ to
+    talk/third_party/expat-2.0.1/. Follow the instructions at
+    http://sourceforge.net/projects/srtp/develop to download latest srtp to
+    talk/third_party/srtp. Note that srtp-1.4.4 does not work since it misses
+    the extensions used by Libjingle.
+    If you put expat or srtp in a different directory, you need to edit
+    talk/libjingle.scons correspondingly.
+
+2.1 Build Libjingle under Linux or OS X
+  * First, make sure the SCONS_DIR environment variable is set correctly.
+  * Second, run talk/third_party/expat-2.0.1/configure and
+    talk/third_party/srtp/configure.
+  * Third, go to the talk/ directory and run $path_to_swtoolkit/hammer.sh. Run
+    $path_to_swtoolkit/hammer.sh --help for information on how to build for
+    different modes.
+
+2.2 Build Libjingle under Windows
+  * First, make sure the SCONS_DIR environment variable is set correctly and
+    Microsoft Visual Studio is installed.
+  * Second, copy talk/third_party/srtp/config.hw to
+    talk/third_party/srtp/crypto/include/config.h.
+  * Third, go to the talk/ directory and run $path_to_swtoolkit/hammer.bat. Run
+    $path_to_swtoolkit/hammer.sh --help for information on how to build for
+    different modes. You can run the last step under Visual Studio Command
+    Prompt if Visual Studio tools are not under the path environment variable.
+
+The built binaries are under talk/build/dbg/staging or talk/build/opt/staging,
+depending on the build mode. When the build is complete, you can run the
+examples, login or call. For the call sample, you can specify the input and
+output RTP dump for voice and video. This package provides two samples of input
+RTP dump: voice.rtpdump is a single channel, 16Khz voice encoded with G722, and
+video.rtpdump is 320x240 video encoded with H264 AVC at 30 frames per second.
+These provided samples will inter-operate with Google Talk Video. If you use
+other input RTP dump, you may need to change the codecs in call_main.cc, lines
+215 - 222.
+
+Libjingle also builds two server tools, a relay server and a STUN server. The
+relay server may be used to relay traffic when a direct peer-to-peer connection
+could not be established. The STUN Server implements the STUN protocol for
+Simple Traversal of UDP over NAT. See the Libjingle Developer Guide at
+http://code.google.com/apis/talk/index.html for information about configuring a
+client to use this relay server and this STUN server.
\ No newline at end of file
diff --git a/README.chrome-sandbox b/README.chrome-sandbox
new file mode 100644
index 0000000..5930d32
--- /dev/null
+++ b/README.chrome-sandbox
@@ -0,0 +1,2 @@
+The chrome-sandbox branch contains some experemental changes that are needed
+to make libjingle work in sandbox in Chrome.
diff --git a/talk/base/Equifax_Secure_Global_eBusiness_CA-1.h b/talk/base/Equifax_Secure_Global_eBusiness_CA-1.h
new file mode 100644
index 0000000..6ff97a6
--- /dev/null
+++ b/talk/base/Equifax_Secure_Global_eBusiness_CA-1.h
@@ -0,0 +1,55 @@
+// This file is the Equifax Secure global eBusiness CA-1 certificate
+// in C form.
+
+// It was generated with the following command line:
+// > openssl x509 -in Equifax_Secure_Global_eBusiness_CA-1.cer -noout -C
+
+// The certificate was retrieved from:
+// http://www.geotrust.com/resources/root_certificates/certificates/Equifax_Secure_Global_eBusiness_CA-1.cer
+
+/* subject:/C=US/O=Equifax Secure Inc./CN=Equifax Secure Global eBusiness CA-1 */
+/* issuer :/C=US/O=Equifax Secure Inc./CN=Equifax Secure Global eBusiness CA-1 */
+unsigned char EquifaxSecureGlobalEBusinessCA1_certificate[660]={
+0x30,0x82,0x02,0x90,0x30,0x82,0x01,0xF9,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01,
+0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x04,0x05,0x00,0x30,
+0x5A,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x1C,
+0x30,0x1A,0x06,0x03,0x55,0x04,0x0A,0x13,0x13,0x45,0x71,0x75,0x69,0x66,0x61,0x78,
+0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x49,0x6E,0x63,0x2E,0x31,0x2D,0x30,0x2B,
+0x06,0x03,0x55,0x04,0x03,0x13,0x24,0x45,0x71,0x75,0x69,0x66,0x61,0x78,0x20,0x53,
+0x65,0x63,0x75,0x72,0x65,0x20,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x20,0x65,0x42,0x75,
+0x73,0x69,0x6E,0x65,0x73,0x73,0x20,0x43,0x41,0x2D,0x31,0x30,0x1E,0x17,0x0D,0x39,
+0x39,0x30,0x36,0x32,0x31,0x30,0x34,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x32,0x30,
+0x30,0x36,0x32,0x31,0x30,0x34,0x30,0x30,0x30,0x30,0x5A,0x30,0x5A,0x31,0x0B,0x30,
+0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x1C,0x30,0x1A,0x06,0x03,
+0x55,0x04,0x0A,0x13,0x13,0x45,0x71,0x75,0x69,0x66,0x61,0x78,0x20,0x53,0x65,0x63,
+0x75,0x72,0x65,0x20,0x49,0x6E,0x63,0x2E,0x31,0x2D,0x30,0x2B,0x06,0x03,0x55,0x04,
+0x03,0x13,0x24,0x45,0x71,0x75,0x69,0x66,0x61,0x78,0x20,0x53,0x65,0x63,0x75,0x72,
+0x65,0x20,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x20,0x65,0x42,0x75,0x73,0x69,0x6E,0x65,
+0x73,0x73,0x20,0x43,0x41,0x2D,0x31,0x30,0x81,0x9F,0x30,0x0D,0x06,0x09,0x2A,0x86,
+0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x81,0x8D,0x00,0x30,0x81,0x89,
+0x02,0x81,0x81,0x00,0xBA,0xE7,0x17,0x90,0x02,0x65,0xB1,0x34,0x55,0x3C,0x49,0xC2,
+0x51,0xD5,0xDF,0xA7,0xD1,0x37,0x8F,0xD1,0xE7,0x81,0x73,0x41,0x52,0x60,0x9B,0x9D,
+0xA1,0x17,0x26,0x78,0xAD,0xC7,0xB1,0xE8,0x26,0x94,0x32,0xB5,0xDE,0x33,0x8D,0x3A,
+0x2F,0xDB,0xF2,0x9A,0x7A,0x5A,0x73,0x98,0xA3,0x5C,0xE9,0xFB,0x8A,0x73,0x1B,0x5C,
+0xE7,0xC3,0xBF,0x80,0x6C,0xCD,0xA9,0xF4,0xD6,0x2B,0xC0,0xF7,0xF9,0x99,0xAA,0x63,
+0xA2,0xB1,0x47,0x02,0x0F,0xD4,0xE4,0x51,0x3A,0x12,0x3C,0x6C,0x8A,0x5A,0x54,0x84,
+0x70,0xDB,0xC1,0xC5,0x90,0xCF,0x72,0x45,0xCB,0xA8,0x59,0xC0,0xCD,0x33,0x9D,0x3F,
+0xA3,0x96,0xEB,0x85,0x33,0x21,0x1C,0x3E,0x1E,0x3E,0x60,0x6E,0x76,0x9C,0x67,0x85,
+0xC5,0xC8,0xC3,0x61,0x02,0x03,0x01,0x00,0x01,0xA3,0x66,0x30,0x64,0x30,0x11,0x06,
+0x09,0x60,0x86,0x48,0x01,0x86,0xF8,0x42,0x01,0x01,0x04,0x04,0x03,0x02,0x00,0x07,
+0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,
+0xFF,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14,0xBE,0xA8,
+0xA0,0x74,0x72,0x50,0x6B,0x44,0xB7,0xC9,0x23,0xD8,0xFB,0xA8,0xFF,0xB3,0x57,0x6B,
+0x68,0x6C,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xBE,0xA8,0xA0,
+0x74,0x72,0x50,0x6B,0x44,0xB7,0xC9,0x23,0xD8,0xFB,0xA8,0xFF,0xB3,0x57,0x6B,0x68,
+0x6C,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x04,0x05,0x00,
+0x03,0x81,0x81,0x00,0x30,0xE2,0x01,0x51,0xAA,0xC7,0xEA,0x5F,0xDA,0xB9,0xD0,0x65,
+0x0F,0x30,0xD6,0x3E,0xDA,0x0D,0x14,0x49,0x6E,0x91,0x93,0x27,0x14,0x31,0xEF,0xC4,
+0xF7,0x2D,0x45,0xF8,0xEC,0xC7,0xBF,0xA2,0x41,0x0D,0x23,0xB4,0x92,0xF9,0x19,0x00,
+0x67,0xBD,0x01,0xAF,0xCD,0xE0,0x71,0xFC,0x5A,0xCF,0x64,0xC4,0xE0,0x96,0x98,0xD0,
+0xA3,0x40,0xE2,0x01,0x8A,0xEF,0x27,0x07,0xF1,0x65,0x01,0x8A,0x44,0x2D,0x06,0x65,
+0x75,0x52,0xC0,0x86,0x10,0x20,0x21,0x5F,0x6C,0x6B,0x0F,0x6C,0xAE,0x09,0x1C,0xAF,
+0xF2,0xA2,0x18,0x34,0xC4,0x75,0xA4,0x73,0x1C,0xF1,0x8D,0xDC,0xEF,0xAD,0xF9,0xB3,
+0x76,0xB4,0x92,0xBF,0xDC,0x95,0x10,0x1E,0xBE,0xCB,0xC8,0x3B,0x5A,0x84,0x60,0x19,
+0x56,0x94,0xA9,0x55,
+};
diff --git a/talk/base/asyncfile.cc b/talk/base/asyncfile.cc
new file mode 100644
index 0000000..5c6e11d
--- /dev/null
+++ b/talk/base/asyncfile.cc
@@ -0,0 +1,38 @@
+/*
+ * 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 "talk/base/asyncfile.h"
+
+namespace talk_base {
+
+AsyncFile::AsyncFile() {
+}
+
+AsyncFile::~AsyncFile() {
+}
+
+}  // namespace talk_base
diff --git a/talk/base/asyncfile.h b/talk/base/asyncfile.h
new file mode 100644
index 0000000..8af52be
--- /dev/null
+++ b/talk/base/asyncfile.h
@@ -0,0 +1,57 @@
+/*
+ * libjingle
+ * Copyright 2004--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.
+ */
+
+#ifndef TALK_BASE_ASYNCFILE_H__
+#define TALK_BASE_ASYNCFILE_H__
+
+#include "talk/base/sigslot.h"
+
+namespace talk_base {
+
+// Provides the ability to perform file I/O asynchronously.
+// TODO: Create a common base class with AsyncSocket.
+class AsyncFile {
+ public:
+  AsyncFile();
+  virtual ~AsyncFile();
+
+  // Determines whether the file will receive read events.
+  virtual bool readable() = 0;
+  virtual void set_readable(bool value) = 0;
+
+  // Determines whether the file will receive write events.
+  virtual bool writable() = 0;
+  virtual void set_writable(bool value) = 0;
+
+  sigslot::signal1<AsyncFile*> SignalReadEvent;
+  sigslot::signal1<AsyncFile*> SignalWriteEvent;
+  sigslot::signal2<AsyncFile*, int> SignalCloseEvent;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_ASYNCFILE_H__
diff --git a/talk/base/asynchttprequest.cc b/talk/base/asynchttprequest.cc
new file mode 100644
index 0000000..1e05d82
--- /dev/null
+++ b/talk/base/asynchttprequest.cc
@@ -0,0 +1,111 @@
+/*
+ * libjingle
+ * Copyright 2004--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 "talk/base/asynchttprequest.h"
+
+namespace talk_base {
+
+enum { MSG_TIMEOUT = SignalThread::ST_MSG_FIRST_AVAILABLE };
+static const int kDefaultHTTPTimeout = 30 * 1000;  // 30 sec
+
+///////////////////////////////////////////////////////////////////////////////
+// AsyncHttpRequest
+///////////////////////////////////////////////////////////////////////////////
+
+AsyncHttpRequest::AsyncHttpRequest(const std::string &user_agent)
+    : firewall_(NULL), port_(80), secure_(false),
+      timeout_(kDefaultHTTPTimeout), fail_redirect_(false),
+      factory_(Thread::Current()->socketserver(), user_agent),
+      pool_(&factory_), client_(user_agent.c_str(), &pool_), error_(HE_NONE)  {
+  client_.SignalHttpClientComplete.connect(this,
+      &AsyncHttpRequest::OnComplete);
+}
+
+AsyncHttpRequest::~AsyncHttpRequest() {
+}
+
+void AsyncHttpRequest::OnWorkStart() {
+  factory_.SetProxy(proxy_);
+  if (secure_)
+    factory_.UseSSL(host_.c_str());
+
+  bool transparent_proxy = (port_ == 80) &&
+           ((proxy_.type == PROXY_HTTPS) || (proxy_.type == PROXY_UNKNOWN));
+  if (transparent_proxy) {
+    client_.set_proxy(proxy_);
+  }
+  client_.set_fail_redirect(fail_redirect_);
+  client_.set_server(SocketAddress(host_, port_));
+
+  LOG(LS_INFO) << "HttpRequest start: " << host_ + client_.request().path;
+
+  Thread::Current()->PostDelayed(timeout_, this, MSG_TIMEOUT);
+  client_.start();
+}
+
+void AsyncHttpRequest::OnWorkStop() {
+  // worker is already quitting, no need to explicitly quit
+  LOG(LS_INFO) << "HttpRequest cancelled";
+}
+
+void AsyncHttpRequest::OnComplete(HttpClient* client, HttpErrorType error) {
+  Thread::Current()->Clear(this, MSG_TIMEOUT);
+
+  set_error(error);
+  if (!error) {
+    LOG(LS_INFO) << "HttpRequest completed successfully";
+
+    std::string value;
+    if (client_.response().hasHeader(HH_LOCATION, &value)) {
+      response_redirect_ = value.c_str();
+    }
+  } else {
+    LOG(LS_INFO) << "HttpRequest completed with error: " << error;
+  }
+
+  worker()->Quit();
+}
+
+void AsyncHttpRequest::OnMessage(Message* message) {
+  if (message->message_id != MSG_TIMEOUT) {
+    SignalThread::OnMessage(message);
+    return;
+  }
+
+  LOG(LS_INFO) << "HttpRequest timed out";
+  client_.reset();
+  worker()->Quit();
+}
+
+void AsyncHttpRequest::DoWork() {
+  // Do nothing while we wait for the request to finish. We only do this so
+  // that we can be a SignalThread; in the future this class should not be
+  // a SignalThread, since it does not need to spawn a new thread.
+  Thread::Current()->ProcessMessages(kForever);
+}
+
+}  // namespace talk_base
diff --git a/talk/base/asynchttprequest.h b/talk/base/asynchttprequest.h
new file mode 100644
index 0000000..f5d7f46
--- /dev/null
+++ b/talk/base/asynchttprequest.h
@@ -0,0 +1,112 @@
+/*
+ * libjingle
+ * Copyright 2004--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.
+ */
+
+#ifndef TALK_BASE_ASYNCHTTPREQUEST_H_
+#define TALK_BASE_ASYNCHTTPREQUEST_H_
+
+#include <string>
+#include "talk/base/event.h"
+#include "talk/base/httpclient.h"
+#include "talk/base/signalthread.h"
+#include "talk/base/socketpool.h"
+#include "talk/base/sslsocketfactory.h"
+
+namespace talk_base {
+
+class FirewallManager;
+
+///////////////////////////////////////////////////////////////////////////////
+// AsyncHttpRequest
+// Performs an HTTP request on a background thread.  Notifies on the foreground
+// thread once the request is done (successfully or unsuccessfully).
+///////////////////////////////////////////////////////////////////////////////
+
+class AsyncHttpRequest : public SignalThread {
+ public:
+  explicit AsyncHttpRequest(const std::string &user_agent);
+  ~AsyncHttpRequest();
+
+  void set_proxy(const ProxyInfo& proxy) {
+    proxy_ = proxy;
+  }
+  void set_firewall(FirewallManager * firewall) {
+    firewall_ = firewall;
+  }
+
+  // The DNS name of the host to connect to.
+  const std::string& host() { return host_; }
+  void set_host(const std::string& host) { host_ = host; }
+
+  // The port to connect to on the target host.
+  int port() { return port_; }
+  void set_port(int port) { port_ = port; }
+
+  // Whether the request should use SSL.
+  bool secure() { return secure_; }
+  void set_secure(bool secure) { secure_ = secure; }
+
+  // Time to wait on the download, in ms.
+  int timeout() { return timeout_; }
+  void set_timeout(int timeout) { timeout_ = timeout; }
+
+  // Fail redirects to allow analysis of redirect urls, etc.
+  bool fail_redirect() const { return fail_redirect_; }
+  void set_fail_redirect(bool redirect) { fail_redirect_ = redirect; }
+
+  // Returns the redirect when redirection occurs
+  const std::string& response_redirect() { return response_redirect_; }
+
+  HttpRequestData& request() { return client_.request(); }
+  HttpResponseData& response() { return client_.response(); }
+  HttpErrorType error() { return error_; }
+
+ protected:
+  void set_error(HttpErrorType error) { error_ = error; }
+  virtual void OnWorkStart();
+  virtual void OnWorkStop();
+  void OnComplete(HttpClient* client, HttpErrorType error);
+  virtual void OnMessage(Message* message);
+  virtual void DoWork();
+
+ private:
+  ProxyInfo proxy_;
+  FirewallManager* firewall_;
+  std::string host_;
+  int port_;
+  bool secure_;
+  int timeout_;
+  bool fail_redirect_;
+  SslSocketFactory factory_;
+  ReuseSocketPool pool_;
+  HttpClient client_;
+  HttpErrorType error_;
+  std::string response_redirect_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_ASYNCHTTPREQUEST_H_
diff --git a/talk/base/asyncpacketsocket.h b/talk/base/asyncpacketsocket.h
new file mode 100644
index 0000000..47a1bf3
--- /dev/null
+++ b/talk/base/asyncpacketsocket.h
@@ -0,0 +1,91 @@
+/*
+ * 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_BASE_ASYNCPACKETSOCKET_H_
+#define TALK_BASE_ASYNCPACKETSOCKET_H_
+
+#include "talk/base/sigslot.h"
+#include "talk/base/socket.h"
+
+namespace talk_base {
+
+// Provides the ability to receive packets asynchronously. Sends are not
+// buffered since it is acceptable to drop packets under high load.
+class AsyncPacketSocket : public sigslot::has_slots<> {
+ public:
+  AsyncPacketSocket() { }
+  virtual ~AsyncPacketSocket() { }
+
+  // Returns current local address. If port or IP address is not
+  // assigned yet, then they set to 0 in the result and |allocated| is
+  // set to false. Otherwise |allocated| is set to true.
+  virtual SocketAddress GetLocalAddress(bool* allocated) const = 0;
+
+  // Returns remote address. Returns zeroes if this is not a client TCP socket.
+  virtual SocketAddress GetRemoteAddress() const = 0;
+
+  // Send a packet.
+  virtual int Send(const void *pv, size_t cb) = 0;
+  virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr) = 0;
+
+  // Close the socket.
+  virtual int Close() = 0;
+
+  // Returns current state of the socket.
+  virtual Socket::ConnState GetState() const = 0;
+
+  // Get/set options.
+  virtual int GetOption(Socket::Option opt, int* value) = 0;
+  virtual int SetOption(Socket::Option opt, int value) = 0;
+
+  // Get/Set current error.
+  // TODO: Do we really need SetError() here?
+  virtual int GetError() const = 0;
+  virtual void SetError(int error) = 0;
+
+  // Emitted after address for the socket is allocated.
+  sigslot::signal2<AsyncPacketSocket*, const SocketAddress&> SignalAddressReady;
+
+  // Emitted each time a packet is read. Used only for UDP and
+  // connected TCP sockets.
+  sigslot::signal4<AsyncPacketSocket*, const char*, size_t,
+                   const SocketAddress&> SignalReadPacket;
+
+  // Used only for connected TCP sockets.
+  sigslot::signal1<AsyncPacketSocket*> SignalConnect;
+  sigslot::signal2<AsyncPacketSocket*, int> SignalClose;
+
+  // Used only for listening TCP sockets.
+  sigslot::signal2<AsyncPacketSocket*, AsyncPacketSocket*> SignalNewConnection;
+
+ private:
+  DISALLOW_EVIL_CONSTRUCTORS(AsyncPacketSocket);
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_ASYNCPACKETSOCKET_H_
diff --git a/talk/base/asyncsocket.cc b/talk/base/asyncsocket.cc
new file mode 100644
index 0000000..d9ed94c
--- /dev/null
+++ b/talk/base/asyncsocket.cc
@@ -0,0 +1,61 @@
+/*
+ * 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 "talk/base/asyncsocket.h"
+
+namespace talk_base {
+
+AsyncSocket::AsyncSocket() {
+}
+
+AsyncSocket::~AsyncSocket() {
+}
+
+AsyncSocketAdapter::AsyncSocketAdapter(AsyncSocket* socket) : socket_(NULL) {
+  Attach(socket);
+}
+
+AsyncSocketAdapter::~AsyncSocketAdapter() {
+  delete socket_;
+}
+
+void AsyncSocketAdapter::Attach(AsyncSocket* socket) {
+  ASSERT(!socket_);
+  socket_ = socket;
+  if (socket_) {
+    socket_->SignalConnectEvent.connect(this,
+        &AsyncSocketAdapter::OnConnectEvent);
+    socket_->SignalReadEvent.connect(this,
+        &AsyncSocketAdapter::OnReadEvent);
+    socket_->SignalWriteEvent.connect(this,
+        &AsyncSocketAdapter::OnWriteEvent);
+    socket_->SignalCloseEvent.connect(this,
+        &AsyncSocketAdapter::OnCloseEvent);
+  }
+}
+
+}  // namespace talk_base
diff --git a/talk/base/asyncsocket.h b/talk/base/asyncsocket.h
new file mode 100644
index 0000000..3d12984
--- /dev/null
+++ b/talk/base/asyncsocket.h
@@ -0,0 +1,133 @@
+/*
+ * libjingle
+ * Copyright 2004--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.
+ */
+
+#ifndef TALK_BASE_ASYNCSOCKET_H_
+#define TALK_BASE_ASYNCSOCKET_H_
+
+#include "talk/base/common.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/socket.h"
+
+namespace talk_base {
+
+// TODO: Remove Socket and rename AsyncSocket to Socket.
+
+// Provides the ability to perform socket I/O asynchronously.
+class AsyncSocket : public Socket {
+ public:
+  AsyncSocket();
+  virtual ~AsyncSocket();
+
+  virtual AsyncSocket* Accept(SocketAddress* paddr) = 0;
+
+  sigslot::signal1<AsyncSocket*> SignalReadEvent;        // ready to read
+  sigslot::signal1<AsyncSocket*> SignalWriteEvent;       // ready to write
+  sigslot::signal1<AsyncSocket*> SignalConnectEvent;     // connected
+  sigslot::signal2<AsyncSocket*, int> SignalCloseEvent;  // closed
+};
+
+class AsyncSocketAdapter : public AsyncSocket, public sigslot::has_slots<> {
+ public:
+  // The adapted socket may explicitly be NULL, and later assigned using Attach.
+  // However, subclasses which support detached mode must override any methods
+  // that will be called during the detached period (usually GetState()), to
+  // avoid dereferencing a null pointer.
+  explicit AsyncSocketAdapter(AsyncSocket* socket);
+  virtual ~AsyncSocketAdapter();
+  void Attach(AsyncSocket* socket);
+  virtual SocketAddress GetLocalAddress() const {
+    return socket_->GetLocalAddress();
+  }
+  virtual SocketAddress GetRemoteAddress() const {
+    return socket_->GetRemoteAddress();
+  }
+  virtual int Bind(const SocketAddress& addr) {
+    return socket_->Bind(addr);
+  }
+  virtual int Connect(const SocketAddress& addr) {
+    return socket_->Connect(addr);
+  }
+  virtual int Send(const void* pv, size_t cb) {
+    return socket_->Send(pv, cb);
+  }
+  virtual int SendTo(const void* pv, size_t cb, const SocketAddress& addr) {
+    return socket_->SendTo(pv, cb, addr);
+  }
+  virtual int Recv(void* pv, size_t cb) {
+    return socket_->Recv(pv, cb);
+  }
+  virtual int RecvFrom(void* pv, size_t cb, SocketAddress* paddr) {
+    return socket_->RecvFrom(pv, cb, paddr);
+  }
+  virtual int Listen(int backlog) {
+    return socket_->Listen(backlog);
+  }
+  virtual AsyncSocket* Accept(SocketAddress* paddr) {
+    return socket_->Accept(paddr);
+  }
+  virtual int Close() {
+    return socket_->Close();
+  }
+  virtual int GetError() const {
+    return socket_->GetError();
+  }
+  virtual void SetError(int error) {
+    return socket_->SetError(error);
+  }
+  virtual ConnState GetState() const {
+    return socket_->GetState();
+  }
+  virtual int EstimateMTU(uint16* mtu) {
+    return socket_->EstimateMTU(mtu);
+  }
+  virtual int GetOption(Option opt, int* value) {
+    return socket_->GetOption(opt, value);
+  }
+  virtual int SetOption(Option opt, int value) {
+    return socket_->SetOption(opt, value);
+  }
+
+ protected:
+  virtual void OnConnectEvent(AsyncSocket* socket) {
+    SignalConnectEvent(this);
+  }
+  virtual void OnReadEvent(AsyncSocket* socket) {
+    SignalReadEvent(this);
+  }
+  virtual void OnWriteEvent(AsyncSocket* socket) {
+    SignalWriteEvent(this);
+  }
+  virtual void OnCloseEvent(AsyncSocket* socket, int err) {
+    SignalCloseEvent(this, err);
+  }
+
+  AsyncSocket* socket_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_ASYNCSOCKET_H_
diff --git a/talk/base/asynctcpsocket.cc b/talk/base/asynctcpsocket.cc
new file mode 100644
index 0000000..03fc01a
--- /dev/null
+++ b/talk/base/asynctcpsocket.cc
@@ -0,0 +1,268 @@
+/*
+ * libjingle
+ * Copyright 2004--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 "talk/base/asynctcpsocket.h"
+
+#include <cstring>
+
+#include "talk/base/byteorder.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+
+#ifdef POSIX
+#include <errno.h>
+#endif  // POSIX
+
+namespace talk_base {
+
+static const size_t MAX_PACKET_SIZE = 64 * 1024;
+
+typedef uint16 PacketLength;
+static const size_t PKT_LEN_SIZE = sizeof(PacketLength);
+
+static const size_t BUF_SIZE = MAX_PACKET_SIZE + PKT_LEN_SIZE;
+
+static const int LISTEN_BACKLOG = 5;
+
+AsyncTCPSocket* AsyncTCPSocket::Create(SocketFactory* factory, bool listen) {
+  AsyncSocket* sock = factory->CreateAsyncSocket(SOCK_STREAM);
+  // This will still return a socket even if we failed to listen on
+  // it. It is neccessary because even if we can't accept new
+  // connections on this socket, the corresponding port is still
+  // useful for outgoing connections.
+  //
+  // TODO: It might be better to pass listen() error to the
+  // upper layer and let it handle the problem.
+  return (sock) ? new AsyncTCPSocket(sock, listen) : NULL;
+}
+
+AsyncTCPSocket::AsyncTCPSocket(AsyncSocket* socket, bool listen)
+    : socket_(socket),
+      listen_(listen),
+      insize_(BUF_SIZE),
+      inpos_(0),
+      outsize_(BUF_SIZE),
+      outpos_(0) {
+  inbuf_ = new char[insize_];
+  outbuf_ = new char[outsize_];
+
+  ASSERT(socket_.get() != NULL);
+  socket_->SignalConnectEvent.connect(this, &AsyncTCPSocket::OnConnectEvent);
+  socket_->SignalReadEvent.connect(this, &AsyncTCPSocket::OnReadEvent);
+  socket_->SignalWriteEvent.connect(this, &AsyncTCPSocket::OnWriteEvent);
+  socket_->SignalCloseEvent.connect(this, &AsyncTCPSocket::OnCloseEvent);
+
+  if (listen_) {
+    if (socket_->Listen(LISTEN_BACKLOG) < 0) {
+      LOG(LS_ERROR) << "Listen() failed with error " << socket_->GetError();
+    }
+  }
+}
+
+AsyncTCPSocket::~AsyncTCPSocket() {
+  delete [] inbuf_;
+  delete [] outbuf_;
+}
+
+SocketAddress AsyncTCPSocket::GetLocalAddress(bool* allocated) const {
+  if (allocated)
+    *allocated = true;
+  return socket_->GetLocalAddress();
+}
+
+SocketAddress AsyncTCPSocket::GetRemoteAddress() const {
+  return socket_->GetRemoteAddress();
+}
+
+int AsyncTCPSocket::Send(const void *pv, size_t cb) {
+  if (cb > MAX_PACKET_SIZE) {
+    socket_->SetError(EMSGSIZE);
+    return -1;
+  }
+
+  // If we are blocking on send, then silently drop this packet
+  if (outpos_)
+    return static_cast<int>(cb);
+
+  PacketLength pkt_len = HostToNetwork16(static_cast<PacketLength>(cb));
+  memcpy(outbuf_, &pkt_len, PKT_LEN_SIZE);
+  memcpy(outbuf_ + PKT_LEN_SIZE, pv, cb);
+  outpos_ = PKT_LEN_SIZE + cb;
+
+  int res = Flush();
+  if (res <= 0) {
+    // drop packet if we made no progress
+    outpos_ = 0;
+    return res;
+  }
+
+  // We claim to have sent the whole thing, even if we only sent partial
+  return static_cast<int>(cb);
+}
+
+int AsyncTCPSocket::SendTo(const void *pv, size_t cb,
+                           const SocketAddress& addr) {
+  if (addr == GetRemoteAddress())
+    return Send(pv, cb);
+
+  ASSERT(false);
+  socket_->SetError(ENOTCONN);
+  return -1;
+}
+
+int AsyncTCPSocket::Close() {
+  return socket_->Close();
+}
+
+Socket::ConnState AsyncTCPSocket::GetState() const {
+  return socket_->GetState();
+}
+
+int AsyncTCPSocket::GetOption(Socket::Option opt, int* value) {
+  return socket_->GetOption(opt, value);
+}
+
+int AsyncTCPSocket::SetOption(Socket::Option opt, int value) {
+  return socket_->SetOption(opt, value);
+}
+
+int AsyncTCPSocket::GetError() const {
+  return socket_->GetError();
+}
+
+void AsyncTCPSocket::SetError(int error) {
+  return socket_->SetError(error);
+}
+
+int AsyncTCPSocket::SendRaw(const void * pv, size_t cb) {
+  if (outpos_ + cb > outsize_) {
+    socket_->SetError(EMSGSIZE);
+    return -1;
+  }
+
+  memcpy(outbuf_ + outpos_, pv, cb);
+  outpos_ += cb;
+
+  return Flush();
+}
+
+void AsyncTCPSocket::ProcessInput(char * data, size_t& len) {
+  SocketAddress remote_addr(GetRemoteAddress());
+
+  while (true) {
+    if (len < PKT_LEN_SIZE)
+      return;
+
+    PacketLength pkt_len;
+    memcpy(&pkt_len, data, PKT_LEN_SIZE);
+    pkt_len = NetworkToHost16(pkt_len);
+
+    if (len < PKT_LEN_SIZE + pkt_len)
+      return;
+
+    SignalReadPacket(this, data + PKT_LEN_SIZE, pkt_len, remote_addr);
+
+    len -= PKT_LEN_SIZE + pkt_len;
+    if (len > 0) {
+      memmove(data, data + PKT_LEN_SIZE + pkt_len, len);
+    }
+  }
+}
+
+int AsyncTCPSocket::Flush() {
+  int res = socket_->Send(outbuf_, outpos_);
+  if (res <= 0) {
+    return res;
+  }
+  if (static_cast<size_t>(res) <= outpos_) {
+    outpos_ -= res;
+  } else {
+    ASSERT(false);
+    return -1;
+  }
+  if (outpos_ > 0) {
+    memmove(outbuf_, outbuf_ + res, outpos_);
+  }
+  return res;
+}
+
+void AsyncTCPSocket::OnConnectEvent(AsyncSocket* socket) {
+  SignalConnect(this);
+}
+
+void AsyncTCPSocket::OnReadEvent(AsyncSocket* socket) {
+  ASSERT(socket_.get() == socket);
+
+  if (listen_) {
+    talk_base::SocketAddress address;
+    talk_base::AsyncSocket* new_socket = socket->Accept(&address);
+    if (!new_socket) {
+      // TODO: Do something better like forwarding the error
+      // to the user.
+      LOG(LS_ERROR) << "TCP accept failed with error " << socket_->GetError();
+      return;
+    }
+
+    SignalNewConnection(this, new AsyncTCPSocket(new_socket, false));
+
+    // Prime a read event in case data is waiting.
+    new_socket->SignalReadEvent(new_socket);
+  } else {
+    int len = socket_->Recv(inbuf_ + inpos_, insize_ - inpos_);
+    if (len < 0) {
+      // TODO: Do something better like forwarding the error to the user.
+      if (!socket_->IsBlocking()) {
+        LOG(LS_ERROR) << "Recv() returned error: " << socket_->GetError();
+      }
+      return;
+    }
+
+    inpos_ += len;
+
+    ProcessInput(inbuf_, inpos_);
+
+    if (inpos_ >= insize_) {
+      LOG(LS_ERROR) << "input buffer overflow";
+      ASSERT(false);
+      inpos_ = 0;
+    }
+  }
+}
+
+void AsyncTCPSocket::OnWriteEvent(AsyncSocket* socket) {
+  ASSERT(socket_.get() == socket);
+
+  if (outpos_ > 0) {
+    Flush();
+  }
+}
+
+void AsyncTCPSocket::OnCloseEvent(AsyncSocket* socket, int error) {
+  SignalClose(this, error);
+}
+
+}  // namespace talk_base
diff --git a/talk/base/asynctcpsocket.h b/talk/base/asynctcpsocket.h
new file mode 100644
index 0000000..06bbbf4
--- /dev/null
+++ b/talk/base/asynctcpsocket.h
@@ -0,0 +1,81 @@
+/*
+ * libjingle
+ * Copyright 2004--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.
+ */
+
+#ifndef TALK_BASE_ASYNCTCPSOCKET_H_
+#define TALK_BASE_ASYNCTCPSOCKET_H_
+
+#include "talk/base/asyncpacketsocket.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/socketfactory.h"
+
+namespace talk_base {
+
+// Simulates UDP semantics over TCP.  Send and Recv packet sizes
+// are preserved, and drops packets silently on Send, rather than
+// buffer them in user space.
+class AsyncTCPSocket : public AsyncPacketSocket {
+ public:
+  static AsyncTCPSocket* Create(SocketFactory* factory, bool listen);
+  explicit AsyncTCPSocket(AsyncSocket* socket, bool listen);
+  virtual ~AsyncTCPSocket();
+
+  virtual SocketAddress GetLocalAddress(bool* allocated) const;
+  virtual SocketAddress GetRemoteAddress() const;
+  virtual int Send(const void *pv, size_t cb);
+  virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr);
+  virtual int Close();
+
+  virtual Socket::ConnState GetState() const;
+  virtual int GetOption(Socket::Option opt, int* value);
+  virtual int SetOption(Socket::Option opt, int value);
+  virtual int GetError() const;
+  virtual void SetError(int error);
+
+ protected:
+  int SendRaw(const void* pv, size_t cb);
+  virtual void ProcessInput(char* data, size_t& len);
+
+ private:
+  int Flush();
+
+  // Called by the underlying socket
+  void OnConnectEvent(AsyncSocket* socket);
+  void OnReadEvent(AsyncSocket* socket);
+  void OnWriteEvent(AsyncSocket* socket);
+  void OnCloseEvent(AsyncSocket* socket, int error);
+
+  scoped_ptr<AsyncSocket> socket_;
+  bool listen_;
+  char* inbuf_, * outbuf_;
+  size_t insize_, inpos_, outsize_, outpos_;
+
+  DISALLOW_EVIL_CONSTRUCTORS(AsyncTCPSocket);
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_ASYNCTCPSOCKET_H_
diff --git a/talk/base/asyncudpsocket.cc b/talk/base/asyncudpsocket.cc
new file mode 100644
index 0000000..dd2dc64
--- /dev/null
+++ b/talk/base/asyncudpsocket.cc
@@ -0,0 +1,129 @@
+/*
+ * 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.
+ */
+
+#if defined(_MSC_VER) && _MSC_VER < 1300
+#pragma warning(disable:4786)
+#endif
+
+#include "talk/base/asyncudpsocket.h"
+#include "talk/base/logging.h"
+
+namespace talk_base {
+
+static const int BUF_SIZE = 64 * 1024;
+
+AsyncUDPSocket* AsyncUDPSocket::Create(SocketFactory* factory,
+                                       const SocketAddress& address) {
+  scoped_ptr<AsyncSocket> socket(factory->CreateAsyncSocket(SOCK_DGRAM));
+  if (!socket.get())
+    return NULL;
+  if (socket->Bind(address)) {
+    LOG(LS_INFO) << "Failed to bind UDP socket " << socket->GetError();
+    return NULL;
+  }
+  return new AsyncUDPSocket(socket.release());
+}
+
+AsyncUDPSocket::AsyncUDPSocket(AsyncSocket* socket)
+    : socket_(socket) {
+  ASSERT(socket_.get() != NULL);
+  size_ = BUF_SIZE;
+  buf_ = new char[size_];
+
+  // The socket should start out readable but not writable.
+  socket_->SignalReadEvent.connect(this, &AsyncUDPSocket::OnReadEvent);
+}
+
+AsyncUDPSocket::~AsyncUDPSocket() {
+  delete [] buf_;
+}
+
+SocketAddress AsyncUDPSocket::GetLocalAddress(bool* allocated) const {
+  if (allocated)
+    *allocated = true;
+  return socket_->GetLocalAddress();
+}
+
+SocketAddress AsyncUDPSocket::GetRemoteAddress() const {
+  return socket_->GetRemoteAddress();
+}
+
+int AsyncUDPSocket::Send(const void *pv, size_t cb) {
+  return socket_->Send(pv, cb);
+}
+
+int AsyncUDPSocket::SendTo(
+    const void *pv, size_t cb, const SocketAddress& addr) {
+  return socket_->SendTo(pv, cb, addr);
+}
+
+int AsyncUDPSocket::Close() {
+  return socket_->Close();
+}
+
+Socket::ConnState AsyncUDPSocket::GetState() const {
+  return socket_->GetState();
+}
+
+int AsyncUDPSocket::GetOption(Socket::Option opt, int* value) {
+  return socket_->GetOption(opt, value);
+}
+
+int AsyncUDPSocket::SetOption(Socket::Option opt, int value) {
+  return socket_->SetOption(opt, value);
+}
+
+int AsyncUDPSocket::GetError() const {
+  return socket_->GetError();
+}
+
+void AsyncUDPSocket::SetError(int error) {
+  return socket_->SetError(error);
+}
+
+void AsyncUDPSocket::OnReadEvent(AsyncSocket* socket) {
+  ASSERT(socket_.get() == socket);
+
+  SocketAddress remote_addr;
+  int len = socket_->RecvFrom(buf_, size_, &remote_addr);
+  if (len < 0) {
+    // An error here typically means we got an ICMP error in response to our
+    // send datagram, indicating the remote address was unreachable.
+    // When doing ICE, this kind of thing will often happen.
+    // TODO: Do something better like forwarding the error to the user.
+    SocketAddress local_addr = socket_->GetLocalAddress();
+    LOG(LS_INFO) << "AsyncUDPSocket[" << local_addr.ToString() << "] "
+                 << "receive failed with error " << socket_->GetError();
+    return;
+  }
+
+  // TODO: Make sure that we got all of the packet.
+  // If we did not, then we should resize our buffer to be large enough.
+  SignalReadPacket(this, buf_, (size_t)len, remote_addr);
+}
+
+}  // namespace talk_base
diff --git a/talk/base/asyncudpsocket.h b/talk/base/asyncudpsocket.h
new file mode 100644
index 0000000..e3012a5
--- /dev/null
+++ b/talk/base/asyncudpsocket.h
@@ -0,0 +1,71 @@
+/*
+ * 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_BASE_ASYNCUDPSOCKET_H_
+#define TALK_BASE_ASYNCUDPSOCKET_H_
+
+#include "talk/base/asyncpacketsocket.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/socketfactory.h"
+
+namespace talk_base {
+
+// Provides the ability to receive packets asynchronously.  Sends are not
+// buffered since it is acceptable to drop packets under high load.
+class AsyncUDPSocket : public AsyncPacketSocket {
+ public:
+  // Creates a new socket for sending asynchronous UDP packets using an
+  // asynchronous socket from the given factory.
+  static AsyncUDPSocket* Create(SocketFactory* factory,
+                                const SocketAddress& address);
+  explicit AsyncUDPSocket(AsyncSocket* socket);
+  virtual ~AsyncUDPSocket();
+
+  virtual SocketAddress GetLocalAddress(bool* allocated) const;
+  virtual SocketAddress GetRemoteAddress() const;
+  virtual int Send(const void *pv, size_t cb);
+  virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr);
+  virtual int Close();
+
+  virtual Socket::ConnState GetState() const;
+  virtual int GetOption(Socket::Option opt, int* value);
+  virtual int SetOption(Socket::Option opt, int value);
+  virtual int GetError() const;
+  virtual void SetError(int error);
+
+ private:
+  // Called when the underlying socket is ready to be read from.
+  void OnReadEvent(AsyncSocket* socket);
+
+  scoped_ptr<AsyncSocket> socket_;
+  char* buf_;
+  size_t size_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_ASYNCUDPSOCKET_H_
diff --git a/talk/base/autodetectproxy.cc b/talk/base/autodetectproxy.cc
new file mode 100644
index 0000000..ffcac26
--- /dev/null
+++ b/talk/base/autodetectproxy.cc
@@ -0,0 +1,187 @@
+/*
+ * 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/base/autodetectproxy.h"
+#include "talk/base/httpcommon.h"
+#include "talk/base/httpcommon-inl.h"
+#include "talk/base/proxydetect.h"
+
+namespace talk_base {
+
+enum { MSG_TIMEOUT = SignalThread::ST_MSG_FIRST_AVAILABLE };
+
+static const ProxyType TEST_ORDER[] = {
+  PROXY_HTTPS, PROXY_SOCKS5, PROXY_UNKNOWN
+};
+
+AutoDetectProxy::AutoDetectProxy(const std::string& user_agent)
+    : agent_(user_agent), socket_(NULL), next_(0) {
+}
+
+AutoDetectProxy::~AutoDetectProxy() {
+}
+
+void AutoDetectProxy::DoWork() {
+  // TODO: Try connecting to server_url without proxy first here?
+  if (!server_url_.empty()) {
+    LOG(LS_INFO) << "GetProxySettingsForUrl(" << server_url_ << ") - start";
+    GetProxySettingsForUrl(agent_.c_str(), server_url_.c_str(), proxy_, true);
+    LOG(LS_INFO) << "GetProxySettingsForUrl - stop";
+  }
+  Url<char> url(proxy_.address.IPAsString());
+  if (url.valid()) {
+    LOG(LS_WARNING) << "AutoDetectProxy removing http prefix on proxy host";
+    proxy_.address.SetIP(url.host());
+  }
+  LOG(LS_INFO) << "AutoDetectProxy found proxy at " << proxy_.address;
+  if (proxy_.type == PROXY_UNKNOWN) {
+    LOG(LS_INFO) << "AutoDetectProxy initiating proxy classification";
+    Next();
+    // Process I/O until Stop()
+    Thread::Current()->ProcessMessages(kForever);
+    // Clean up the autodetect socket, from the thread that created it
+    delete socket_;
+  }
+  // TODO: If we found a proxy, try to use it to verify that it
+  // works by sending a request to server_url. This could either be
+  // done here or by the HttpPortAllocator.
+}
+
+void AutoDetectProxy::OnMessage(Message *msg) {
+  if (MSG_TIMEOUT == msg->message_id) {
+    OnCloseEvent(socket_, ETIMEDOUT);
+  } else {
+    SignalThread::OnMessage(msg);
+  }
+}
+
+void AutoDetectProxy::Next() {
+  if (TEST_ORDER[next_] >= PROXY_UNKNOWN) {
+    Complete(PROXY_UNKNOWN);
+    return;
+  }
+
+  LOG(LS_VERBOSE) << "AutoDetectProxy connecting to "
+                  << proxy_.address.ToString();
+
+  if (socket_) {
+    Thread::Current()->Clear(this, MSG_TIMEOUT);
+    socket_->Close();
+    Thread::Current()->Dispose(socket_);
+    socket_ = NULL;
+  }
+
+  socket_ = Thread::Current()->socketserver()->CreateAsyncSocket(SOCK_STREAM);
+  socket_->SignalConnectEvent.connect(this, &AutoDetectProxy::OnConnectEvent);
+  socket_->SignalReadEvent.connect(this, &AutoDetectProxy::OnReadEvent);
+  socket_->SignalCloseEvent.connect(this, &AutoDetectProxy::OnCloseEvent);
+  socket_->Connect(proxy_.address);
+
+  // Timeout after 2 seconds
+  Thread::Current()->PostDelayed(2000, this, MSG_TIMEOUT);
+}
+
+void AutoDetectProxy::Complete(ProxyType type) {
+  Thread::Current()->Clear(this, MSG_TIMEOUT);
+  socket_->Close();
+
+  proxy_.type = type;
+  LoggingSeverity sev = (proxy_.type == PROXY_UNKNOWN) ? LS_ERROR : LS_INFO;
+  LOG_V(sev) << "AutoDetectProxy detected " << proxy_.address.ToString()
+             << " as type " << proxy_.type;
+
+  Thread::Current()->Quit();
+}
+
+void AutoDetectProxy::OnConnectEvent(AsyncSocket * socket) {
+  std::string probe;
+
+  switch (TEST_ORDER[next_]) {
+    case PROXY_HTTPS:
+      probe.assign("CONNECT www.google.com:443 HTTP/1.0\r\n"
+                   "User-Agent: ");
+      probe.append(agent_);
+      probe.append("\r\n"
+                   "Host: www.google.com\r\n"
+                   "Content-Length: 0\r\n"
+                   "Proxy-Connection: Keep-Alive\r\n"
+                   "\r\n");
+      break;
+    case PROXY_SOCKS5:
+      probe.assign("\005\001\000", 3);
+      break;
+    default:
+      ASSERT(false);
+      return;
+  }
+
+  LOG(LS_VERBOSE) << "AutoDetectProxy probing type " << TEST_ORDER[next_]
+                  << " sending " << probe.size() << " bytes";
+  socket_->Send(probe.data(), probe.size());
+}
+
+void AutoDetectProxy::OnReadEvent(AsyncSocket * socket) {
+  char data[257];
+  int len = socket_->Recv(data, 256);
+  if (len > 0) {
+    data[len] = 0;
+    LOG(LS_VERBOSE) << "AutoDetectProxy read " << len << " bytes";
+  }
+
+  switch (TEST_ORDER[next_]) {
+    case PROXY_HTTPS:
+      if ((len >= 2) && (data[0] == '\x05')) {
+        Complete(PROXY_SOCKS5);
+        return;
+      }
+      if ((len >= 5) && (strncmp(data, "HTTP/", 5) == 0)) {
+        Complete(PROXY_HTTPS);
+        return;
+      }
+      break;
+    case PROXY_SOCKS5:
+      if ((len >= 2) && (data[0] == '\x05')) {
+        Complete(PROXY_SOCKS5);
+        return;
+      }
+      break;
+    default:
+      ASSERT(false);
+      return;
+  }
+
+  ++next_;
+  Next();
+}
+
+void AutoDetectProxy::OnCloseEvent(AsyncSocket * socket, int error) {
+  LOG(LS_VERBOSE) << "AutoDetectProxy closed with error: " << error;
+  ++next_;
+  Next();
+}
+
+}  // namespace talk_base
diff --git a/talk/base/autodetectproxy.h b/talk/base/autodetectproxy.h
new file mode 100644
index 0000000..6bb2a4b
--- /dev/null
+++ b/talk/base/autodetectproxy.h
@@ -0,0 +1,90 @@
+/*
+ * 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_BASE_AUTODETECTPROXY_H_
+#define TALK_BASE_AUTODETECTPROXY_H_
+
+#include <string>
+
+#include "talk/base/cryptstring.h"
+#include "talk/base/proxyinfo.h"
+#include "talk/base/signalthread.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// AutoDetectProxy
+///////////////////////////////////////////////////////////////////////////////
+
+class AsyncSocket;
+
+class AutoDetectProxy : public SignalThread {
+ public:
+  explicit AutoDetectProxy(const std::string& user_agent);
+
+  const ProxyInfo& proxy() const { return proxy_; }
+
+  void set_server_url(const std::string& url) {
+    server_url_ = url;
+  }
+  void set_proxy(const SocketAddress& proxy) {
+    proxy_.type = PROXY_UNKNOWN;
+    proxy_.address = proxy;
+  }
+  void set_auth_info(bool use_auth, const std::string& username,
+                     const CryptString& password) {
+    if (use_auth) {
+      proxy_.username = username;
+      proxy_.password = password;
+    }
+  }
+
+ protected:
+  virtual ~AutoDetectProxy();
+
+  // SignalThread Interface
+  virtual void DoWork();
+  virtual void OnMessage(Message *msg);
+
+  void Next();
+  void Complete(ProxyType type);
+
+  void OnConnectEvent(AsyncSocket * socket);
+  void OnReadEvent(AsyncSocket * socket);
+  void OnCloseEvent(AsyncSocket * socket, int error);
+
+ private:
+  std::string agent_;
+  std::string server_url_;
+  ProxyInfo proxy_;
+  AsyncSocket* socket_;
+  int next_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_AUTODETECTPROXY_H_
diff --git a/talk/base/base64.cc b/talk/base/base64.cc
new file mode 100644
index 0000000..363c378
--- /dev/null
+++ b/talk/base/base64.cc
@@ -0,0 +1,243 @@
+
+//*********************************************************************
+//* Base64 - a simple base64 encoder and decoder.
+//*
+//*     Copyright (c) 1999, Bob Withers - bwit@pobox.com
+//*
+//* This code may be freely used for any purpose, either personal
+//* or commercial, provided the authors copyright notice remains
+//* intact.
+//*
+//* Enhancements by Stanley Yamane:
+//*     o reverse lookup table for the decode function
+//*     o reserve string buffer space in advance
+//*
+//*********************************************************************
+
+#include "talk/base/base64.h"
+#include "talk/base/common.h"
+
+using std::string;
+using std::vector;
+
+namespace talk_base {
+
+static const char kPad = '=';
+static const unsigned char pd = 0xFD;  // Padding
+static const unsigned char sp = 0xFE;  // Whitespace
+static const unsigned char il = 0xFF;  // Illegal base64 character
+
+const string Base64::Base64Table(
+// 0000000000111111111122222222223333333333444444444455555555556666
+// 0123456789012345678901234567890123456789012345678901234567890123
+  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
+
+// Decode Table gives the index of any valid base64 character in the
+// Base64 table
+// 65 == A, 97 == a, 48 == 0, 43 == +, 47 == /
+
+const unsigned char Base64::DecodeTable[] = {
+// 0  1  2  3  4  5  6  7  8  9
+  il,il,il,il,il,il,il,il,il,sp,  //   0 -   9
+  sp,sp,sp,sp,il,il,il,il,il,il,  //  10 -  19
+  il,il,il,il,il,il,il,il,il,il,  //  20 -  29
+  il,il,sp,il,il,il,il,il,il,il,  //  30 -  39
+  il,il,il,62,il,il,il,63,52,53,  //  40 -  49
+  54,55,56,57,58,59,60,61,il,il,  //  50 -  59
+  il,pd,il,il,il, 0, 1, 2, 3, 4,  //  60 -  69
+   5, 6, 7, 8, 9,10,11,12,13,14,  //  70 -  79
+  15,16,17,18,19,20,21,22,23,24,  //  80 -  89
+  25,il,il,il,il,il,il,26,27,28,  //  90 -  99
+  29,30,31,32,33,34,35,36,37,38,  // 100 - 109
+  39,40,41,42,43,44,45,46,47,48,  // 110 - 119
+  49,50,51,il,il,il,il,il,il,il,  // 120 - 129
+  il,il,il,il,il,il,il,il,il,il,  // 130 - 139
+  il,il,il,il,il,il,il,il,il,il,  // 140 - 149
+  il,il,il,il,il,il,il,il,il,il,  // 150 - 159
+  il,il,il,il,il,il,il,il,il,il,  // 160 - 169
+  il,il,il,il,il,il,il,il,il,il,  // 170 - 179
+  il,il,il,il,il,il,il,il,il,il,  // 180 - 189
+  il,il,il,il,il,il,il,il,il,il,  // 190 - 199
+  il,il,il,il,il,il,il,il,il,il,  // 200 - 209
+  il,il,il,il,il,il,il,il,il,il,  // 210 - 219
+  il,il,il,il,il,il,il,il,il,il,  // 220 - 229
+  il,il,il,il,il,il,il,il,il,il,  // 230 - 239
+  il,il,il,il,il,il,il,il,il,il,  // 240 - 249
+  il,il,il,il,il,il               // 250 - 255
+};
+
+bool Base64::IsBase64Char(char ch) {
+  return (('A' <= ch) && (ch <= 'Z')) ||
+         (('a' <= ch) && (ch <= 'z')) ||
+         (('0' <= ch) && (ch <= '9')) ||
+         (ch == '+') || (ch == '/');
+}
+
+bool Base64::IsBase64Encoded(const std::string& str) {
+  for (size_t i = 0; i < str.size(); ++i) {
+    if (!IsBase64Char(str.at(i)))
+      return false;
+  }
+  return true;
+}
+
+void Base64::EncodeFromArray(const void* data, size_t len, string* result) {
+  ASSERT(NULL != result);
+  result->clear();
+  result->reserve(((len + 2) / 3) * 4);
+  const unsigned char* byte_data = static_cast<const unsigned char*>(data);
+
+  unsigned char c;
+  size_t i = 0;
+  while (i < len) {
+    c = (byte_data[i] >> 2) & 0x3f;
+    result->push_back(Base64Table[c]);
+
+    c = (byte_data[i] << 4) & 0x3f;
+    if (++i < len) {
+      c |= (byte_data[i] >> 4) & 0x0f;
+    }
+    result->push_back(Base64Table[c]);
+
+    if (i < len) {
+      c = (byte_data[i] << 2) & 0x3f;
+      if (++i < len) {
+        c |= (byte_data[i] >> 6) & 0x03;
+      }
+      result->push_back(Base64Table[c]);
+    } else {
+      result->push_back(kPad);
+    }
+
+    if (i < len) {
+      c = byte_data[i] & 0x3f;
+      result->push_back(Base64Table[c]);
+      ++i;
+    } else {
+      result->push_back(kPad);
+    }
+  }
+}
+
+size_t Base64::GetNextQuantum(DecodeFlags parse_flags, bool illegal_pads,
+                              const char* data, size_t len, size_t* dpos,
+                              unsigned char qbuf[4], bool* padded)
+{
+  size_t byte_len = 0, pad_len = 0, pad_start = 0;
+  for (; (byte_len < 4) && (*dpos < len); ++*dpos) {
+    qbuf[byte_len] = DecodeTable[static_cast<unsigned char>(data[*dpos])];
+    if ((il == qbuf[byte_len]) || (illegal_pads && (pd == qbuf[byte_len]))) {
+      if (parse_flags != DO_PARSE_ANY)
+        break;
+      // Ignore illegal characters
+    } else if (sp == qbuf[byte_len]) {
+      if (parse_flags == DO_PARSE_STRICT)
+        break;
+      // Ignore spaces
+    } else if (pd == qbuf[byte_len]) {
+      if (byte_len < 2) {
+        if (parse_flags != DO_PARSE_ANY)
+          break;
+        // Ignore unexpected padding
+      } else if (byte_len + pad_len >= 4) {
+        if (parse_flags != DO_PARSE_ANY)
+          break;
+        // Ignore extra pads
+      } else {
+        if (1 == ++pad_len) {
+          pad_start = *dpos;
+        }
+      }
+    } else {
+      if (pad_len > 0) {
+        if (parse_flags != DO_PARSE_ANY)
+          break;
+        // Ignore pads which are followed by data
+        pad_len = 0;
+      }
+      ++byte_len;
+    }
+  }
+  for (size_t i = byte_len; i < 4; ++i) {
+    qbuf[i] = 0;
+  }
+  if (4 == byte_len + pad_len) {
+    *padded = true;
+  } else {
+    *padded = false;
+    if (pad_len) {
+      // Roll back illegal padding
+      *dpos = pad_start;
+    }
+  }
+  return byte_len;
+}
+
+bool Base64::DecodeFromArray(const char* data, size_t len, DecodeFlags flags,
+                             string* result, size_t* data_used) {
+  return DecodeFromArrayTemplate<string>(data, len, flags, result, data_used);
+}
+
+bool Base64::DecodeFromArray(const char* data, size_t len, DecodeFlags flags,
+                             vector<char>* result, size_t* data_used) {
+  return DecodeFromArrayTemplate<vector<char> >(data, len, flags, result,
+                                                data_used);
+}
+
+template<typename T>
+bool Base64::DecodeFromArrayTemplate(const char* data, size_t len,
+                                     DecodeFlags flags, T* result,
+                                     size_t* data_used)
+{
+  ASSERT(NULL != result);
+  ASSERT(flags <= (DO_PARSE_MASK | DO_PAD_MASK | DO_TERM_MASK));
+
+  const DecodeFlags parse_flags = flags & DO_PARSE_MASK;
+  const DecodeFlags pad_flags   = flags & DO_PAD_MASK;
+  const DecodeFlags term_flags  = flags & DO_TERM_MASK;
+  ASSERT(0 != parse_flags);
+  ASSERT(0 != pad_flags);
+  ASSERT(0 != term_flags);
+
+  result->clear();
+  result->reserve(len);
+
+  size_t dpos = 0;
+  bool success = true, padded;
+  unsigned char c, qbuf[4];
+  while (dpos < len) {
+    size_t qlen = GetNextQuantum(parse_flags, (DO_PAD_NO == pad_flags),
+                                 data, len, &dpos, qbuf, &padded);
+    c = (qbuf[0] << 2) | ((qbuf[1] >> 4) & 0x3);
+    if (qlen >= 2) {
+      result->push_back(c);
+      c = ((qbuf[1] << 4) & 0xf0) | ((qbuf[2] >> 2) & 0xf);
+      if (qlen >= 3) {
+        result->push_back(c);
+        c = ((qbuf[2] << 6) & 0xc0) | qbuf[3];
+        if (qlen >= 4) {
+          result->push_back(c);
+          c = 0;
+        }
+      }
+    }
+    if (qlen < 4) {
+      if ((DO_TERM_ANY != term_flags) && (0 != c)) {
+        success = false;  // unused bits
+      }
+      if ((DO_PAD_YES == pad_flags) && !padded) {
+        success = false;  // expected padding
+      }
+      break;
+    }
+  }
+  if ((DO_TERM_BUFFER == term_flags) && (dpos != len)) {
+    success = false;  // unused chars
+  }
+  if (data_used) {
+    *data_used = dpos;
+  }
+  return success;
+}
+
+} // namespace talk_base
diff --git a/talk/base/base64.h b/talk/base/base64.h
new file mode 100644
index 0000000..73904dc
--- /dev/null
+++ b/talk/base/base64.h
@@ -0,0 +1,96 @@
+
+//*********************************************************************
+//* C_Base64 - a simple base64 encoder and decoder.
+//*
+//*     Copyright (c) 1999, Bob Withers - bwit@pobox.com
+//*
+//* This code may be freely used for any purpose, either personal
+//* or commercial, provided the authors copyright notice remains
+//* intact.
+//*********************************************************************
+
+#ifndef TALK_BASE_BASE64_H__
+#define TALK_BASE_BASE64_H__
+
+#include <string>
+#include <vector>
+
+namespace talk_base {
+
+class Base64
+{
+public:
+  enum DecodeOption {
+    DO_PARSE_STRICT =  1,  // Parse only base64 characters
+    DO_PARSE_WHITE  =  2,  // Parse only base64 and whitespace characters
+    DO_PARSE_ANY    =  3,  // Parse all characters
+    DO_PARSE_MASK   =  3,
+
+    DO_PAD_YES      =  4,  // Padding is required
+    DO_PAD_ANY      =  8,  // Padding is optional
+    DO_PAD_NO       = 12,  // Padding is disallowed
+    DO_PAD_MASK     = 12,
+
+    DO_TERM_BUFFER  = 16,  // Must termiante at end of buffer
+    DO_TERM_CHAR    = 32,  // May terminate at any character boundary
+    DO_TERM_ANY     = 48,  // May terminate at a sub-character bit offset
+    DO_TERM_MASK    = 48,
+
+    // Strictest interpretation
+    DO_STRICT = DO_PARSE_STRICT | DO_PAD_YES | DO_TERM_BUFFER,
+
+    DO_LAX    = DO_PARSE_ANY | DO_PAD_ANY | DO_TERM_CHAR,
+  };
+  typedef int DecodeFlags;
+
+  static bool IsBase64Char(char ch);
+
+  // Determines whether the given string consists entirely of valid base64
+  // encoded characters.
+  static bool IsBase64Encoded(const std::string& str);
+
+  static void EncodeFromArray(const void* data, size_t len,
+                              std::string* result);
+  static bool DecodeFromArray(const char* data, size_t len, DecodeFlags flags,
+                              std::string* result, size_t* data_used);
+  static bool DecodeFromArray(const char* data, size_t len, DecodeFlags flags,
+                              std::vector<char>* result, size_t* data_used);
+
+  // Convenience Methods
+  static inline std::string Encode(const std::string& data) {
+    std::string result;
+    EncodeFromArray(data.data(), data.size(), &result);
+    return result;
+  }
+  static inline std::string Decode(const std::string& data, DecodeFlags flags) {
+    std::string result;
+    DecodeFromArray(data.data(), data.size(), flags, &result, NULL);
+    return result;
+  }
+  static inline bool Decode(const std::string& data, DecodeFlags flags,
+                            std::string* result, size_t* data_used)
+  {
+    return DecodeFromArray(data.data(), data.size(), flags, result, data_used);
+  }
+  static inline bool Decode(const std::string& data, DecodeFlags flags,
+                            std::vector<char>* result, size_t* data_used)
+  {
+    return DecodeFromArray(data.data(), data.size(), flags, result, data_used);
+  }
+
+private:
+  static const std::string Base64Table;
+  static const unsigned char DecodeTable[];
+
+  static size_t GetNextQuantum(DecodeFlags parse_flags, bool illegal_pads,
+                               const char* data, size_t len, size_t* dpos,
+                               unsigned char qbuf[4], bool* padded);
+  template<typename T>
+  static bool DecodeFromArrayTemplate(const char* data, size_t len,
+                                      DecodeFlags flags, T* result,
+                                      size_t* data_used);
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_BASE64_H__
diff --git a/talk/base/basicdefs.h b/talk/base/basicdefs.h
new file mode 100644
index 0000000..0e58bf6
--- /dev/null
+++ b/talk/base/basicdefs.h
@@ -0,0 +1,37 @@
+/*
+ * 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_BASE_BASICDEFS_H__
+#define TALK_BASE_BASICDEFS_H__
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#define ARRAY_SIZE(x) (static_cast<int>((sizeof(x)/sizeof(x[0]))))
+
+#endif // TAKL_BASE_BASICDEFS_H__
diff --git a/talk/base/basicpacketsocketfactory.cc b/talk/base/basicpacketsocketfactory.cc
new file mode 100644
index 0000000..20173d1
--- /dev/null
+++ b/talk/base/basicpacketsocketfactory.cc
@@ -0,0 +1,159 @@
+/*
+ * 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.
+ */
+
+#include "talk/base/basicpacketsocketfactory.h"
+
+#include "talk/base/asyncudpsocket.h"
+#include "talk/base/asynctcpsocket.h"
+#include "talk/base/logging.h"
+#include "talk/base/socketadapters.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+BasicPacketSocketFactory::BasicPacketSocketFactory(
+    Thread* thread)
+    : thread_(thread),
+      socket_factory_(NULL) {
+}
+
+BasicPacketSocketFactory::BasicPacketSocketFactory(
+    SocketFactory* socket_factory)
+    : thread_(NULL),
+      socket_factory_(socket_factory) {
+}
+
+BasicPacketSocketFactory::~BasicPacketSocketFactory() {
+}
+
+AsyncPacketSocket* BasicPacketSocketFactory::CreateUdpSocket(
+    const SocketAddress& address, int min_port, int max_port) {
+  // UDP sockets are simple.
+  talk_base::AsyncSocket* socket =
+      socket_factory()->CreateAsyncSocket(SOCK_DGRAM);
+  if (!socket) {
+    return NULL;
+  }
+  if (BindSocket(socket, address, min_port, max_port) < 0) {
+    LOG(LS_ERROR) << "UDP bind failed with error "
+                    << socket->GetError();
+    delete socket;
+    return NULL;
+  }
+  return new talk_base::AsyncUDPSocket(socket);
+}
+
+AsyncPacketSocket* BasicPacketSocketFactory::CreateServerTcpSocket(
+    const SocketAddress& local_address, int min_port, int max_port,
+    bool listen, bool ssl) {
+  talk_base::AsyncSocket* socket =
+      socket_factory()->CreateAsyncSocket(SOCK_STREAM);
+  if (!socket) {
+    return NULL;
+  }
+
+  if (BindSocket(socket, local_address, min_port, max_port) < 0) {
+    LOG(LS_ERROR) << "TCP bind failed with error "
+                  << socket->GetError();
+    delete socket;
+    return NULL;
+  }
+
+  // If using SSLTCP, wrap the TCP socket in a pseudo-SSL socket.
+  if (ssl) {
+    socket = new talk_base::AsyncSSLSocket(socket);
+  }
+
+  return new talk_base::AsyncTCPSocket(socket, true);
+}
+
+AsyncPacketSocket* BasicPacketSocketFactory::CreateClientTcpSocket(
+    const SocketAddress& local_address, const SocketAddress& remote_address,
+    const ProxyInfo& proxy_info, const std::string& user_agent, bool ssl) {
+  talk_base::AsyncSocket* socket =
+      socket_factory()->CreateAsyncSocket(SOCK_STREAM);
+  if (!socket) {
+    return NULL;
+  }
+
+  if (BindSocket(socket, local_address, 0, 0) < 0) {
+    LOG(LS_ERROR) << "TCP bind failed with error "
+                  << socket->GetError();
+    delete socket;
+    return NULL;
+  }
+
+  // If using a proxy, wrap the socket in a proxy socket.
+  if (proxy_info.type == talk_base::PROXY_SOCKS5) {
+    socket = new talk_base::AsyncSocksProxySocket(
+        socket, proxy_info.address, proxy_info.username, proxy_info.password);
+  } else if (proxy_info.type == talk_base::PROXY_HTTPS) {
+    socket = new talk_base::AsyncHttpsProxySocket(
+        socket, user_agent, proxy_info.address,
+        proxy_info.username, proxy_info.password);
+  }
+
+  // If using SSLTCP, wrap the TCP socket in a pseudo-SSL socket.
+  if (ssl) {
+    socket = new talk_base::AsyncSSLSocket(socket);
+  }
+
+  if (socket->Connect(remote_address) < 0) {
+    LOG(LS_ERROR) << "TCP connect failed with error "
+                  << socket->GetError();
+    delete socket;
+    return NULL;
+  }
+
+  // Finally, wrap that socket in a TCP packet socket.
+  return new talk_base::AsyncTCPSocket(socket, false);
+}
+
+int BasicPacketSocketFactory::BindSocket(
+    AsyncSocket* socket, const SocketAddress& local_address,
+    int min_port, int max_port) {
+  int ret = -1;
+  if (min_port == 0 && max_port == 0) {
+    // If there's no port range, let the OS pick a port for us.
+    ret = socket->Bind(local_address);
+  } else {
+    // Otherwise, try to find a port in the provided range.
+    for (int port = min_port; ret < 0 && port <= max_port; ++port) {
+      ret = socket->Bind(talk_base::SocketAddress(local_address.ip(), port));
+    }
+  }
+  return ret;
+}
+
+SocketFactory* BasicPacketSocketFactory::socket_factory() {
+  if (thread_)
+    return thread_->socketserver();
+  else
+    return socket_factory_;
+}
+
+}  // namespace talk_base
diff --git a/talk/base/basicpacketsocketfactory.h b/talk/base/basicpacketsocketfactory.h
new file mode 100644
index 0000000..2f39d07
--- /dev/null
+++ b/talk/base/basicpacketsocketfactory.h
@@ -0,0 +1,67 @@
+/*
+ * 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_BASE_BASICPACKETSOCKETFACTORY_H_
+#define TALK_BASE_BASICPACKETSOCKETFACTORY_H_
+
+#include "talk/base/asyncsocket.h"
+#include "talk/base/packetsocketfactory.h"
+
+namespace talk_base {
+
+class AsyncSocket;
+class SocketFactory;
+class Thread;
+
+class BasicPacketSocketFactory : public PacketSocketFactory {
+ public:
+  explicit BasicPacketSocketFactory(Thread* thread);
+  explicit BasicPacketSocketFactory(SocketFactory* socket_factory);
+  virtual ~BasicPacketSocketFactory();
+
+  virtual AsyncPacketSocket* CreateUdpSocket(
+      const SocketAddress& local_address, int min_port, int max_port);
+  virtual AsyncPacketSocket* CreateServerTcpSocket(
+      const SocketAddress& local_address, int min_port, int max_port,
+      bool listen, bool ssl);
+  virtual AsyncPacketSocket* CreateClientTcpSocket(
+      const SocketAddress& local_address, const SocketAddress& remote_address,
+      const ProxyInfo& proxy_info, const std::string& user_agent, bool ssl);
+
+ private:
+  int BindSocket(AsyncSocket* socket, const SocketAddress& local_address,
+                 int min_port, int max_port);
+
+  SocketFactory* socket_factory();
+
+  Thread* thread_;
+  SocketFactory* socket_factory_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_BASICPACKETSOCKETFACTORY_H_
diff --git a/talk/base/basictypes.h b/talk/base/basictypes.h
new file mode 100644
index 0000000..a26a3ff
--- /dev/null
+++ b/talk/base/basictypes.h
@@ -0,0 +1,122 @@
+/*
+ * 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_BASE_BASICTYPES_H__
+#define TALK_BASE_BASICTYPES_H__
+
+#ifndef WIN32
+#include <stdint.h>  // for uintptr_t
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "talk/base/constructormagic.h"
+
+#ifndef INT_TYPES_DEFINED
+#define INT_TYPES_DEFINED
+#ifdef COMPILER_MSVC
+typedef __int64 int64;
+#else
+typedef long long int64;
+#endif /* COMPILER_MSVC */
+typedef int int32;
+typedef short int16;
+typedef char int8;
+
+#ifdef COMPILER_MSVC
+typedef unsigned __int64 uint64;
+typedef __int64 int64;
+#ifndef INT64_C
+#define INT64_C(x) x ## I64
+#endif
+#ifndef UINT64_C
+#define UINT64_C(x) x ## UI64
+#endif
+#define INT64_F "I64"
+#else
+typedef unsigned long long uint64;
+typedef long long int64;
+#ifndef INT64_C
+#define INT64_C(x) x ## LL
+#endif
+#ifndef UINT64_C
+#define UINT64_C(x) x ## ULL
+#endif
+#define INT64_F "ll"
+#endif /* COMPILER_MSVC */
+typedef unsigned int uint32;
+typedef unsigned short uint16;
+typedef unsigned char uint8;
+#endif  // INT_TYPES_DEFINED
+
+#ifdef WIN32
+typedef int socklen_t;
+#endif
+
+namespace talk_base {
+  template<class T> inline T _min(T a, T b) { return (a > b) ? b : a; }
+  template<class T> inline T _max(T a, T b) { return (a < b) ? b : a; }
+
+  // For wait functions that take a number of milliseconds, kForever indicates
+  // unlimited time.
+  const int kForever = -1;
+}
+
+// Detect compiler is for x86 or x64.
+#if defined(__x86_64__) || defined(_M_X64) || \
+    defined(__i386__) || defined(_M_IX86)
+#define CPU_X86 1
+#endif
+
+#ifdef WIN32
+#define alignof(t) __alignof(t)
+#else  // !WIN32
+#define alignof(t) __alignof__(t)
+#endif  // !WIN32
+#define IS_ALIGNED(p, a) (0==(reinterpret_cast<uintptr_t>(p) & ((a)-1)))
+#define ALIGNP(p, t) \
+  (reinterpret_cast<uint8*>(((reinterpret_cast<uintptr_t>(p) + \
+  ((t)-1)) & ~((t)-1))))
+
+#ifndef UNUSED
+#define UNUSED(x) Unused(static_cast<const void *>(&x))
+#define UNUSED2(x,y) Unused(static_cast<const void *>(&x)); Unused(static_cast<const void *>(&y))
+#define UNUSED3(x,y,z) Unused(static_cast<const void *>(&x)); Unused(static_cast<const void *>(&y)); Unused(static_cast<const void *>(&z))
+#define UNUSED4(x,y,z,a) Unused(static_cast<const void *>(&x)); Unused(static_cast<const void *>(&y)); Unused(static_cast<const void *>(&z)); Unused(static_cast<const void *>(&a))
+#define UNUSED5(x,y,z,a,b) Unused(static_cast<const void *>(&x)); Unused(static_cast<const void *>(&y)); Unused(static_cast<const void *>(&z)); Unused(static_cast<const void *>(&a)); Unused(static_cast<const void *>(&b))
+inline void Unused(const void *) { }
+#endif // UNUSED
+
+#if defined(__GNUC__)
+#define GCC_ATTR(x) __attribute__ ((x))
+#else  // !__GNUC__
+#define GCC_ATTR(x)
+#endif  // !__GNUC__
+
+#endif // TALK_BASE_BASICTYPES_H__
diff --git a/talk/base/buffer.h b/talk/base/buffer.h
new file mode 100644
index 0000000..311cfad
--- /dev/null
+++ b/talk/base/buffer.h
@@ -0,0 +1,119 @@
+/*
+ * libjingle
+ * Copyright 2004-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.
+ */
+
+#ifndef TALK_BASE_BUFFER_H_
+#define TALK_BASE_BUFFER_H_
+
+#include <cstring>
+
+#include "talk/base/scoped_ptr.h"
+
+namespace talk_base {
+
+// Basic buffer class, can be grown and shrunk dynamically.
+// Unlike std::string/vector, does not initialize data when expanding capacity.
+class Buffer {
+ public:
+  Buffer() {
+    Construct(NULL, 0, 0);
+  }
+  Buffer(const void* data, size_t length) {
+    Construct(data, length, length);
+  }
+  Buffer(const void* data, size_t length, size_t capacity) {
+    Construct(data, length, capacity);
+  }
+  Buffer(const Buffer& buf) {
+    Construct(buf.data(), buf.length(), buf.length());
+  }
+
+  const char* data() const { return data_.get(); }
+  char* data() { return data_.get(); }
+  // TODO: should this be size(), like STL?
+  size_t length() const { return length_; }
+  size_t capacity() const { return capacity_; }
+
+  Buffer& operator=(const Buffer& buf) {
+    if (&buf != this) {
+      Construct(buf.data(), buf.length(), buf.length());
+    }
+    return *this;
+  }
+  bool operator==(const Buffer& buf) const {
+    return (length_ == buf.length() &&
+            memcmp(data_.get(), buf.data(), length_) == 0);
+  }
+  bool operator!=(const Buffer& buf) const {
+    return !operator==(buf);
+  }
+
+  void SetData(const void* data, size_t length) {
+    ASSERT(data != NULL || length == 0);
+    SetLength(length);
+    memcpy(data_.get(), data, length);
+  }
+  void AppendData(const void* data, size_t length) {
+    ASSERT(data != NULL || length == 0);
+    size_t old_length = length_;
+    SetLength(length_ + length);
+    memcpy(data_.get() + old_length, data, length);
+  }
+  void SetLength(size_t length) {
+    SetCapacity(length);
+    length_ = length;
+  }
+  void SetCapacity(size_t capacity) {
+    if (capacity > capacity_) {
+      talk_base::scoped_array<char> data(new char[capacity]);
+      memcpy(data.get(), data_.get(), length_);
+      data_.swap(data);
+      capacity_ = capacity;
+    }
+  }
+
+  void TransferTo(Buffer* buf) {
+    ASSERT(buf != NULL);
+    buf->data_.reset(data_.release());
+    buf->length_ = length_;
+    buf->capacity_ = capacity_;
+    Construct(NULL, 0, 0);
+  }
+
+ protected:
+  void Construct(const void* data, size_t length, size_t capacity) {
+    data_.reset(new char[capacity_ = capacity]);
+    SetData(data, length);
+  }
+
+  scoped_array<char> data_;
+  size_t length_;
+  size_t capacity_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_BUFFER_H_
diff --git a/talk/base/bytebuffer.cc b/talk/base/bytebuffer.cc
new file mode 100644
index 0000000..9aef159
--- /dev/null
+++ b/talk/base/bytebuffer.cc
@@ -0,0 +1,211 @@
+/*
+ * 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/base/bytebuffer.h"
+
+#include <algorithm>
+#include <cassert>
+#include <cstring>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/byteorder.h"
+
+namespace talk_base {
+
+static const int DEFAULT_SIZE = 4096;
+
+ByteBuffer::ByteBuffer() {
+  start_ = 0;
+  end_   = 0;
+  size_  = DEFAULT_SIZE;
+  bytes_ = new char[size_];
+}
+
+ByteBuffer::ByteBuffer(const char* bytes, size_t len) {
+  start_ = 0;
+  end_   = len;
+  size_  = len;
+  bytes_ = new char[size_];
+  memcpy(bytes_, bytes, end_);
+}
+
+ByteBuffer::ByteBuffer(const char* bytes) {
+  start_ = 0;
+  end_   = strlen(bytes);
+  size_  = end_;
+  bytes_ = new char[size_];
+  memcpy(bytes_, bytes, end_);
+}
+
+ByteBuffer::~ByteBuffer() {
+  delete[] bytes_;
+}
+
+bool ByteBuffer::ReadUInt8(uint8* val) {
+  if (!val) return false;
+
+  return ReadBytes(reinterpret_cast<char*>(val), 1);
+}
+
+bool ByteBuffer::ReadUInt16(uint16* val) {
+  if (!val) return false;
+
+  uint16 v;
+  if (!ReadBytes(reinterpret_cast<char*>(&v), 2)) {
+    return false;
+  } else {
+    *val = NetworkToHost16(v);
+    return true;
+  }
+}
+
+bool ByteBuffer::ReadUInt24(uint32* val) {
+  if (!val) return false;
+
+  uint32 v = 0;
+  if (!ReadBytes(reinterpret_cast<char*>(&v) + 1, 3)) {
+    return false;
+  } else {
+    *val = NetworkToHost32(v);
+    return true;
+  }
+}
+
+bool ByteBuffer::ReadUInt32(uint32* val) {
+  if (!val) return false;
+
+  uint32 v;
+  if (!ReadBytes(reinterpret_cast<char*>(&v), 4)) {
+    return false;
+  } else {
+    *val = NetworkToHost32(v);
+    return true;
+  }
+}
+
+bool ByteBuffer::ReadUInt64(uint64* val) {
+  if (!val) return false;
+
+  uint64 v;
+  if (!ReadBytes(reinterpret_cast<char*>(&v), 8)) {
+    return false;
+  } else {
+    *val = NetworkToHost64(v);
+    return true;
+  }
+}
+
+bool ByteBuffer::ReadString(std::string* val, size_t len) {
+  if (!val) return false;
+
+  if (len > Length()) {
+    return false;
+  } else {
+    val->append(bytes_ + start_, len);
+    start_ += len;
+    return true;
+  }
+}
+
+bool ByteBuffer::ReadBytes(char* val, size_t len) {
+  if (len > Length()) {
+    return false;
+  } else {
+    memcpy(val, bytes_ + start_, len);
+    start_ += len;
+    return true;
+  }
+}
+
+void ByteBuffer::WriteUInt8(uint8 val) {
+  WriteBytes(reinterpret_cast<const char*>(&val), 1);
+}
+
+void ByteBuffer::WriteUInt16(uint16 val) {
+  uint16 v = HostToNetwork16(val);
+  WriteBytes(reinterpret_cast<const char*>(&v), 2);
+}
+
+void ByteBuffer::WriteUInt24(uint32 val) {
+  uint32 v = HostToNetwork32(val);
+  WriteBytes(reinterpret_cast<const char*>(&v) + 1, 3);
+}
+
+void ByteBuffer::WriteUInt32(uint32 val) {
+  uint32 v = HostToNetwork32(val);
+  WriteBytes(reinterpret_cast<const char*>(&v), 4);
+}
+
+void ByteBuffer::WriteUInt64(uint64 val) {
+  uint64 v = HostToNetwork64(val);
+  WriteBytes(reinterpret_cast<const char*>(&v), 8);
+}
+
+void ByteBuffer::WriteString(const std::string& val) {
+  WriteBytes(val.c_str(), val.size());
+}
+
+void ByteBuffer::WriteBytes(const char* val, size_t len) {
+  if (Length() + len > Capacity())
+    Resize(Length() + len);
+
+  memcpy(bytes_ + end_, val, len);
+  end_ += len;
+}
+
+void ByteBuffer::Resize(size_t size) {
+  if (size > size_)
+    size = _max(size, 3 * size_ / 2);
+
+  size_t len = _min(end_ - start_, size);
+  char* new_bytes = new char[size];
+  memcpy(new_bytes, bytes_ + start_, len);
+  delete [] bytes_;
+
+  start_ = 0;
+  end_   = len;
+  size_  = size;
+  bytes_ = new_bytes;
+}
+
+void ByteBuffer::Consume(size_t size) {
+  if (size > Length())
+    return;
+
+  start_ += size;
+}
+
+void ByteBuffer::Shift(size_t size) {
+  if (size > Length())
+    return;
+
+  end_ = Length() - size;
+  memmove(bytes_, bytes_ + start_ + size, end_);
+  start_ = 0;
+}
+
+}  // namespace talk_base
diff --git a/talk/base/bytebuffer.h b/talk/base/bytebuffer.h
new file mode 100644
index 0000000..bb162ec
--- /dev/null
+++ b/talk/base/bytebuffer.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 TALK_BASE_BYTEBUFFER_H_
+#define TALK_BASE_BYTEBUFFER_H_
+
+#include <string>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/constructormagic.h"
+
+namespace talk_base {
+
+class ByteBuffer {
+ public:
+  ByteBuffer();
+  ByteBuffer(const char* bytes, size_t len);
+  explicit ByteBuffer(const char* bytes);  // uses strlen
+  ~ByteBuffer();
+
+  const char* Data() const { return bytes_ + start_; }
+  size_t Length() const { return end_ - start_; }
+  size_t Capacity() const { return size_ - start_; }
+
+  bool ReadUInt8(uint8* val);
+  bool ReadUInt16(uint16* val);
+  bool ReadUInt24(uint32* val);
+  bool ReadUInt32(uint32* val);
+  bool ReadUInt64(uint64* val);
+  bool ReadString(std::string* val, size_t len);  // append to val
+  bool ReadBytes(char* val, size_t len);
+
+  void WriteUInt8(uint8 val);
+  void WriteUInt16(uint16 val);
+  void WriteUInt24(uint32 val);
+  void WriteUInt32(uint32 val);
+  void WriteUInt64(uint64 val);
+  void WriteString(const std::string& val);
+  void WriteBytes(const char* val, size_t len);
+
+  void Resize(size_t size);
+  void Consume(size_t size);
+  void Shift(size_t size);
+
+ private:
+  char* bytes_;
+  size_t size_;
+  size_t start_;
+  size_t end_;
+
+  // There are sensible ways to define these, but they aren't needed in our code
+  // base.
+  DISALLOW_COPY_AND_ASSIGN(ByteBuffer);
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_BYTEBUFFER_H_
diff --git a/talk/base/byteorder.h b/talk/base/byteorder.h
new file mode 100644
index 0000000..08094b8
--- /dev/null
+++ b/talk/base/byteorder.h
@@ -0,0 +1,172 @@
+/*
+ * 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_BASE_BYTEORDER_H__
+#define TALK_BASE_BYTEORDER_H__
+
+#ifdef POSIX
+#include <arpa/inet.h>
+#endif
+
+#ifdef WIN32
+#include <winsock2.h>
+#endif
+
+#include "talk/base/basictypes.h"
+
+namespace talk_base {
+
+// Reading and writing of little and big-endian numbers from memory
+// TODO: Add HostEndian #defines (HE)
+// TODO: Consider NetworkEndian as synonym for BigEndian, for clarity in use.
+// TODO: Consider creating optimized versions, such as direct read/writes of
+// integers in host-endian format, when the platform supports it.
+
+inline void Set8(void* memory, size_t offset, uint8 v) {
+  static_cast<uint8*>(memory)[offset] = v;
+}
+inline uint8 Get8(const void* memory, size_t offset) {
+  return static_cast<const uint8*>(memory)[offset];
+}
+
+inline void SetBE16(void* memory, uint16 v) {
+  Set8(memory, 0, static_cast<uint8>(v >>  8));
+  Set8(memory, 1, static_cast<uint8>(v >>  0));
+}
+inline void SetBE32(void* memory, uint32 v) {
+  Set8(memory, 0, static_cast<uint8>(v >> 24));
+  Set8(memory, 1, static_cast<uint8>(v >> 16));
+  Set8(memory, 2, static_cast<uint8>(v >>  8));
+  Set8(memory, 3, static_cast<uint8>(v >>  0));
+}
+inline void SetBE64(void* memory, uint64 v) {
+  Set8(memory, 0, static_cast<uint8>(v >> 56));
+  Set8(memory, 1, static_cast<uint8>(v >> 48));
+  Set8(memory, 2, static_cast<uint8>(v >> 40));
+  Set8(memory, 3, static_cast<uint8>(v >> 32));
+  Set8(memory, 4, static_cast<uint8>(v >> 24));
+  Set8(memory, 5, static_cast<uint8>(v >> 16));
+  Set8(memory, 6, static_cast<uint8>(v >>  8));
+  Set8(memory, 7, static_cast<uint8>(v >>  0));
+}
+inline uint16 GetBE16(const void* memory) {
+  return (static_cast<uint16>(Get8(memory, 0)) << 8)
+       | (static_cast<uint16>(Get8(memory, 1)) << 0);
+}
+inline uint32 GetBE32(const void* memory) {
+  return (static_cast<uint32>(Get8(memory, 0)) << 24)
+       | (static_cast<uint32>(Get8(memory, 1)) << 16)
+       | (static_cast<uint32>(Get8(memory, 2)) <<  8)
+       | (static_cast<uint32>(Get8(memory, 3)) <<  0);
+}
+inline uint64 GetBE64(const void* memory) {
+  return (static_cast<uint64>(Get8(memory, 0)) << 56)
+       | (static_cast<uint64>(Get8(memory, 1)) << 48)
+       | (static_cast<uint64>(Get8(memory, 2)) << 40)
+       | (static_cast<uint64>(Get8(memory, 3)) << 32)
+       | (static_cast<uint64>(Get8(memory, 4)) << 24)
+       | (static_cast<uint64>(Get8(memory, 5)) << 16)
+       | (static_cast<uint64>(Get8(memory, 6)) <<  8)
+       | (static_cast<uint64>(Get8(memory, 7)) <<  0);
+}
+
+inline void SetLE16(void* memory, uint16 v) {
+  Set8(memory, 1, static_cast<uint8>(v >>  8));
+  Set8(memory, 0, static_cast<uint8>(v >>  0));
+}
+inline void SetLE32(void* memory, uint32 v) {
+  Set8(memory, 3, static_cast<uint8>(v >> 24));
+  Set8(memory, 2, static_cast<uint8>(v >> 16));
+  Set8(memory, 1, static_cast<uint8>(v >>  8));
+  Set8(memory, 0, static_cast<uint8>(v >>  0));
+}
+inline void SetLE64(void* memory, uint64 v) {
+  Set8(memory, 7, static_cast<uint8>(v >> 56));
+  Set8(memory, 6, static_cast<uint8>(v >> 48));
+  Set8(memory, 5, static_cast<uint8>(v >> 40));
+  Set8(memory, 4, static_cast<uint8>(v >> 32));
+  Set8(memory, 3, static_cast<uint8>(v >> 24));
+  Set8(memory, 2, static_cast<uint8>(v >> 16));
+  Set8(memory, 1, static_cast<uint8>(v >>  8));
+  Set8(memory, 0, static_cast<uint8>(v >>  0));
+}
+inline uint16 GetLE16(const void* memory) {
+  return (static_cast<uint16>(Get8(memory, 1)) << 8)
+       | (static_cast<uint16>(Get8(memory, 0)) << 0);
+}
+inline uint32 GetLE32(const void* memory) {
+  return (static_cast<uint32>(Get8(memory, 3)) << 24)
+       | (static_cast<uint32>(Get8(memory, 2)) << 16)
+       | (static_cast<uint32>(Get8(memory, 1)) <<  8)
+       | (static_cast<uint32>(Get8(memory, 0)) <<  0);
+}
+inline uint64 GetLE64(const void* memory) {
+  return (static_cast<uint64>(Get8(memory, 7)) << 56)
+       | (static_cast<uint64>(Get8(memory, 6)) << 48)
+       | (static_cast<uint64>(Get8(memory, 5)) << 40)
+       | (static_cast<uint64>(Get8(memory, 4)) << 32)
+       | (static_cast<uint64>(Get8(memory, 3)) << 24)
+       | (static_cast<uint64>(Get8(memory, 2)) << 16)
+       | (static_cast<uint64>(Get8(memory, 1)) <<  8)
+       | (static_cast<uint64>(Get8(memory, 0)) <<  0);
+}
+
+// Check if the current host is big endian.
+inline bool IsHostBigEndian() {
+  static const int number = 1;
+  return (0 == *reinterpret_cast<const char*>(&number));
+}
+
+inline uint16 HostToNetwork16(uint16 n) {
+  return htons(n);
+}
+
+inline uint32 HostToNetwork32(uint32 n) {
+  return htonl(n);
+}
+
+inline uint64 HostToNetwork64(uint64 n) {
+  // If the host is little endian, GetBE64 converts n to big network endian.
+  return IsHostBigEndian() ? n : GetBE64(&n);
+}
+
+inline uint16 NetworkToHost16(uint16 n) {
+  return ntohs(n);
+}
+
+inline uint32 NetworkToHost32(uint32 n) {
+  return ntohl(n);
+}
+
+inline uint64 NetworkToHost64(uint64 n) {
+  // If the host is little endian, GetBE64 converts n to little endian.
+  return IsHostBigEndian() ? n : GetBE64(&n);
+}
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_BYTEORDER_H__
diff --git a/talk/base/checks.cc b/talk/base/checks.cc
new file mode 100644
index 0000000..5466783
--- /dev/null
+++ b/talk/base/checks.cc
@@ -0,0 +1,47 @@
+/*
+ * libjingle
+ * Copyright 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 <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#include "talk/base/checks.h"
+#include "talk/base/logging.h"
+
+void Fatal(const char* file, int line, const char* format, ...) {
+  char msg[256];
+
+  va_list arguments;
+  va_start(arguments, format);
+  vsnprintf(msg, sizeof(msg), format, arguments);
+  va_end(arguments);
+
+  LOG(LS_ERROR) << "\n\n#\n# Fatal error in " << file
+                << ", line " << line << "\n#" << msg
+                << "\n#\n";
+  abort();
+}
diff --git a/talk/base/checks.h b/talk/base/checks.h
new file mode 100644
index 0000000..12adc25
--- /dev/null
+++ b/talk/base/checks.h
@@ -0,0 +1,44 @@
+/*
+ * libjingle
+ * Copyright 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.
+ */
+
+// This module contains some basic debugging facilities.
+
+
+#ifndef SHARED_COMMANDLINEFLAGS_CHECKS_H__
+#define SHARED_COMMANDLINEFLAGS_CHECKS_H__
+
+#include <string.h>
+
+// Prints an error message to stderr and aborts execution.
+void Fatal(const char* file, int line, const char* format, ...);
+
+
+// The UNREACHABLE macro is very useful during development.
+#define UNREACHABLE()                                   \
+  Fatal(__FILE__, __LINE__, "unreachable code")
+
+#endif  // SHARED_COMMANDLINEFLAGS_CHECKS_H__
diff --git a/talk/base/common.cc b/talk/base/common.cc
new file mode 100644
index 0000000..59e73d5
--- /dev/null
+++ b/talk/base/common.cc
@@ -0,0 +1,71 @@
+/*
+ * 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 <stdlib.h>
+#include <stdio.h>
+#include <memory.h>
+
+#if WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#endif  // WIN32
+
+#if OSX
+#include <CoreServices/CoreServices.h>
+#endif  // OSX
+
+#include <algorithm>
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+
+//////////////////////////////////////////////////////////////////////
+// Assertions
+//////////////////////////////////////////////////////////////////////
+
+namespace talk_base {
+
+void Break() {
+#if WIN32
+  ::DebugBreak();
+#elif OSX  // !WIN32
+  ::Debugger();
+#else // !OSX && !WIN32
+#if _DEBUG_HAVE_BACKTRACE
+  OutputTrace();
+#endif
+  abort();
+#endif // !OSX && !WIN32
+}
+
+void LogAssert(const char * function, const char * file, int line,
+               const char * expression) {
+  // TODO - if we put hooks in here, we can do a lot fancier logging
+  LOG(LS_ERROR) << file << "(" << line << ")" << ": ASSERT FAILED: "
+                << expression << " @ " << function;
+}
+
+} // namespace talk_base
diff --git a/talk/base/common.h b/talk/base/common.h
new file mode 100644
index 0000000..ba9c5ae
--- /dev/null
+++ b/talk/base/common.h
@@ -0,0 +1,138 @@
+/*
+ * 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_BASE_COMMON_H_
+#define TALK_BASE_COMMON_H_
+
+#include "talk/base/constructormagic.h"
+
+#if defined(_MSC_VER)
+// warning C4355: 'this' : used in base member initializer list
+#pragma warning(disable:4355)
+#endif
+
+//////////////////////////////////////////////////////////////////////
+// General Utilities
+//////////////////////////////////////////////////////////////////////
+
+#ifndef UNUSED
+#define UNUSED(x) Unused(static_cast<const void *>(&x))
+#define UNUSED2(x,y) Unused(static_cast<const void *>(&x)); Unused(static_cast<const void *>(&y))
+#define UNUSED3(x,y,z) Unused(static_cast<const void *>(&x)); Unused(static_cast<const void *>(&y)); Unused(static_cast<const void *>(&z))
+#define UNUSED4(x,y,z,a) Unused(static_cast<const void *>(&x)); Unused(static_cast<const void *>(&y)); Unused(static_cast<const void *>(&z)); Unused(static_cast<const void *>(&a))
+#define UNUSED5(x,y,z,a,b) Unused(static_cast<const void *>(&x)); Unused(static_cast<const void *>(&y)); Unused(static_cast<const void *>(&z)); Unused(static_cast<const void *>(&a)); Unused(static_cast<const void *>(&b))
+inline void Unused(const void *) { }
+#endif // UNUSED
+
+#ifndef WIN32
+#define strnicmp(x,y,n) strncasecmp(x,y,n)
+#define stricmp(x,y) strcasecmp(x,y)
+
+// TODO: Remove this. std::max should be used everywhere in the code.
+// NOMINMAX must be defined where we include <windows.h>.
+#define stdmax(x,y) std::max(x,y)
+#else
+#define stdmax(x,y) talk_base::_max(x,y)
+#endif
+
+
+#define ARRAY_SIZE(x) (static_cast<int>((sizeof(x)/sizeof(x[0]))))
+
+/////////////////////////////////////////////////////////////////////////////
+// Assertions
+/////////////////////////////////////////////////////////////////////////////
+
+#ifndef ENABLE_DEBUG
+#define ENABLE_DEBUG _DEBUG
+#endif  // !defined(ENABLE_DEBUG)
+
+#if ENABLE_DEBUG
+
+namespace talk_base {
+
+// Break causes the debugger to stop executing, or the program to abort
+void Break();
+
+// LogAssert writes information about an assertion to the log
+void LogAssert(const char * function, const char * file, int line,
+               const char * expression);
+
+inline bool Assert(bool result, const char * function, const char * file,
+                   int line, const char * expression) {
+  if (!result) {
+    LogAssert(function, file, line, expression);
+    Break();
+    return false;
+  }
+  return true;
+}
+
+}  // namespace talk_base
+
+#if defined(_MSC_VER) && _MSC_VER < 1300
+#define __FUNCTION__ ""
+#endif
+
+#ifndef ASSERT
+#define ASSERT(x) (void)talk_base::Assert((x),__FUNCTION__,__FILE__,__LINE__,#x)
+#endif
+
+#ifndef VERIFY
+#define VERIFY(x) talk_base::Assert((x),__FUNCTION__,__FILE__,__LINE__,#x)
+#endif
+
+#else // !ENABLE_DEBUG
+
+namespace talk_base {
+
+inline bool ImplicitCastToBool(bool result) { return result; }
+
+}  // namespace talk_base
+
+#ifndef ASSERT
+#define ASSERT(x) (void)0
+#endif
+
+#ifndef VERIFY
+#define VERIFY(x) talk_base::ImplicitCastToBool(x)
+#endif
+
+#endif // !ENABLE_DEBUG
+
+#define COMPILE_TIME_ASSERT(expr)       char CTA_UNIQUE_NAME[expr]
+#define CTA_UNIQUE_NAME                 MAKE_NAME(__LINE__)
+#define CTA_MAKE_NAME(line)             MAKE_NAME2(line)
+#define CTA_MAKE_NAME2(line)            constraint_ ## line
+
+#ifdef __GNUC__
+// Forces compiler to inline, even against its better judgement. Use wisely.
+#define FORCE_INLINE __attribute__((always_inline))
+#else
+#define FORCE_INLINE
+#endif
+
+#endif // TALK_BASE_COMMON_H_
diff --git a/talk/base/constructormagic.h b/talk/base/constructormagic.h
new file mode 100644
index 0000000..8b1f7ff
--- /dev/null
+++ b/talk/base/constructormagic.h
@@ -0,0 +1,55 @@
+/*
+ * 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_BASE_CONSTRUCTORMAGIC_H_
+#define TALK_BASE_CONSTRUCTORMAGIC_H_
+
+#define DISALLOW_ASSIGN(TypeName) \
+  void operator=(const TypeName&)
+
+// A macro to disallow the evil copy constructor and operator= functions
+// This should be used in the private: declarations for a class
+#define DISALLOW_COPY_AND_ASSIGN(TypeName)    \
+  TypeName(const TypeName&);                    \
+  DISALLOW_ASSIGN(TypeName)
+
+// Alternative, less-accurate legacy name.
+#define DISALLOW_EVIL_CONSTRUCTORS(TypeName) \
+  DISALLOW_COPY_AND_ASSIGN(TypeName)
+
+// A macro to disallow all the implicit constructors, namely the
+// default constructor, copy constructor and operator= functions.
+//
+// This should be used in the private: declarations for a class
+// that wants to prevent anyone from instantiating it. This is
+// especially useful for classes containing only static methods.
+#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \
+  TypeName();                                    \
+  DISALLOW_EVIL_CONSTRUCTORS(TypeName)
+
+
+#endif  // TALK_BASE_CONSTRUCTORMAGIC_H_
diff --git a/talk/base/criticalsection.h b/talk/base/criticalsection.h
new file mode 100644
index 0000000..ee3db1a
--- /dev/null
+++ b/talk/base/criticalsection.h
@@ -0,0 +1,131 @@
+/*
+ * 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_BASE_CRITICALSECTION_H__
+#define TALK_BASE_CRITICALSECTION_H__
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#endif
+
+#ifdef POSIX
+#include <pthread.h>
+#endif
+
+#ifdef _DEBUG
+#define CS_TRACK_OWNER 1
+#endif  // _DEBUG
+
+#if CS_TRACK_OWNER
+#define TRACK_OWNER(x) x
+#else  // !CS_TRACK_OWNER
+#define TRACK_OWNER(x)
+#endif  // !CS_TRACK_OWNER
+
+namespace talk_base {
+
+#ifdef WIN32
+class CriticalSection {
+public:
+  CriticalSection() {
+    InitializeCriticalSection(&crit_);
+    // Windows docs say 0 is not a valid thread id
+    TRACK_OWNER(thread_ = 0);
+  }
+  ~CriticalSection() {
+    DeleteCriticalSection(&crit_);
+  }
+  void Enter() {
+    EnterCriticalSection(&crit_);
+    TRACK_OWNER(thread_ = GetCurrentThreadId());
+  }
+  void Leave() {
+    TRACK_OWNER(thread_ = 0);
+    LeaveCriticalSection(&crit_);
+  }
+
+#if CS_TRACK_OWNER
+  bool CurrentThreadIsOwner() const { return thread_ == GetCurrentThreadId(); }
+#endif  // CS_TRACK_OWNER
+
+private:
+  CRITICAL_SECTION crit_;
+  TRACK_OWNER(DWORD thread_);  // The section's owning thread id
+};
+#endif // WIN32
+
+#ifdef POSIX
+class CriticalSection {
+public:
+  CriticalSection() {
+    pthread_mutexattr_t mutex_attribute;
+    pthread_mutexattr_init(&mutex_attribute);
+    pthread_mutexattr_settype(&mutex_attribute, PTHREAD_MUTEX_RECURSIVE);
+    pthread_mutex_init(&mutex_, &mutex_attribute);
+    pthread_mutexattr_destroy(&mutex_attribute);
+    TRACK_OWNER(thread_ = 0);
+  }
+  ~CriticalSection() {
+    pthread_mutex_destroy(&mutex_);
+  }
+  void Enter() {
+    pthread_mutex_lock(&mutex_);
+    TRACK_OWNER(thread_ = pthread_self());
+  }
+  void Leave() {
+    TRACK_OWNER(thread_ = 0);
+    pthread_mutex_unlock(&mutex_);
+  }
+
+#if CS_TRACK_OWNER
+  bool CurrentThreadIsOwner() const { return pthread_equal(thread_, pthread_self()); }
+#endif  // CS_TRACK_OWNER
+
+private:
+  pthread_mutex_t mutex_;
+  TRACK_OWNER(pthread_t thread_);
+};
+#endif // POSIX
+
+// CritScope, for serializing exection through a scope
+
+class CritScope {
+public:
+  CritScope(CriticalSection *pcrit) {
+    pcrit_ = pcrit;
+    pcrit_->Enter();
+  }
+  ~CritScope() {
+    pcrit_->Leave();
+  }
+private:
+  CriticalSection *pcrit_;
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_CRITICALSECTION_H__
diff --git a/talk/base/cryptstring.h b/talk/base/cryptstring.h
new file mode 100644
index 0000000..eb39be2
--- /dev/null
+++ b/talk/base/cryptstring.h
@@ -0,0 +1,198 @@
+/*
+ * 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_BASE_CRYPTSTRING_H_
+#define _TALK_BASE_CRYPTSTRING_H_
+
+#include <cstring>
+#include <string>
+#include <vector>
+#include "talk/base/linked_ptr.h"
+#include "talk/base/scoped_ptr.h"
+
+namespace talk_base {
+
+class CryptStringImpl {
+public:
+  virtual ~CryptStringImpl() {}
+  virtual size_t GetLength() const = 0;
+  virtual void CopyTo(char * dest, bool nullterminate) const = 0;
+  virtual std::string UrlEncode() const = 0;
+  virtual CryptStringImpl * Copy() const = 0;
+  virtual void CopyRawTo(std::vector<unsigned char> * dest) const = 0;
+};
+
+class EmptyCryptStringImpl : public CryptStringImpl {
+public:
+  virtual ~EmptyCryptStringImpl() {}
+  virtual size_t GetLength() const { return 0; }
+  virtual void CopyTo(char * dest, bool nullterminate) const {
+    if (nullterminate) {
+      *dest = '\0';
+    }
+  }
+  virtual std::string UrlEncode() const { return ""; }
+  virtual CryptStringImpl * Copy() const { return new EmptyCryptStringImpl(); }
+  virtual void CopyRawTo(std::vector<unsigned char> * dest) const {
+    dest->clear();
+  }
+};
+
+class CryptString {
+public:
+  CryptString() : impl_(new EmptyCryptStringImpl()) {}
+  size_t GetLength() const { return impl_->GetLength(); }
+  void CopyTo(char * dest, bool nullterminate) const { impl_->CopyTo(dest, nullterminate); }
+  CryptString(const CryptString & other) : impl_(other.impl_->Copy()) {}
+  explicit CryptString(const CryptStringImpl & impl) : impl_(impl.Copy()) {}
+  CryptString & operator=(const CryptString & other) {
+    if (this != &other) {
+      impl_.reset(other.impl_->Copy());
+    }
+    return *this;
+  }
+  void Clear() { impl_.reset(new EmptyCryptStringImpl()); }
+  std::string UrlEncode() const { return impl_->UrlEncode(); }
+  void CopyRawTo(std::vector<unsigned char> * dest) const {
+    return impl_->CopyRawTo(dest);
+  }
+  
+private:
+  scoped_ptr<const CryptStringImpl> impl_;
+};
+
+
+// Used for constructing strings where a password is involved and we
+// need to ensure that we zero memory afterwards
+class FormatCryptString {
+public:
+  FormatCryptString() {
+    storage_ = new char[32];
+    capacity_ = 32;
+    length_ = 0;
+    storage_[0] = 0;
+  }
+  
+  void Append(const std::string & text) {
+    Append(text.data(), text.length());
+  }
+
+  void Append(const char * data, size_t length) {
+    EnsureStorage(length_ + length + 1);
+    memcpy(storage_ + length_, data, length);
+    length_ += length;
+    storage_[length_] = '\0';
+  }
+  
+  void Append(const CryptString * password) {
+    size_t len = password->GetLength();
+    EnsureStorage(length_ + len + 1);
+    password->CopyTo(storage_ + length_, true);
+    length_ += len;
+  }
+
+  size_t GetLength() {
+    return length_;
+  }
+
+  const char * GetData() {
+    return storage_;
+  }
+
+
+  // Ensures storage of at least n bytes
+  void EnsureStorage(size_t n) {
+    if (capacity_ >= n) {
+      return;
+    }
+
+    size_t old_capacity = capacity_;
+    char * old_storage = storage_;
+
+    for (;;) {
+      capacity_ *= 2;
+      if (capacity_ >= n)
+        break;
+    }
+
+    storage_ = new char[capacity_];
+
+    if (old_capacity) {
+      memcpy(storage_, old_storage, length_);
+    
+      // zero memory in a way that an optimizer won't optimize it out
+      old_storage[0] = 0;
+      for (size_t i = 1; i < old_capacity; i++) {
+        old_storage[i] = old_storage[i - 1];
+      }
+      delete[] old_storage;
+    }
+  }  
+
+  ~FormatCryptString() {
+    if (capacity_) {
+      storage_[0] = 0;
+      for (size_t i = 1; i < capacity_; i++) {
+        storage_[i] = storage_[i - 1];
+      }
+    }
+    delete[] storage_;
+  }
+private:
+  char * storage_;
+  size_t capacity_;
+  size_t length_;
+};
+
+class InsecureCryptStringImpl : public CryptStringImpl {
+ public:
+  std::string& password() { return password_; }
+  const std::string& password() const { return password_; }
+
+  virtual ~InsecureCryptStringImpl() {}
+  virtual size_t GetLength() const { return password_.size(); }
+  virtual void CopyTo(char * dest, bool nullterminate) const {
+    memcpy(dest, password_.data(), password_.size());
+    if (nullterminate) dest[password_.size()] = 0;
+  }
+  virtual std::string UrlEncode() const { return password_; }
+  virtual CryptStringImpl * Copy() const {
+    InsecureCryptStringImpl * copy = new InsecureCryptStringImpl;
+    copy->password() = password_;
+    return copy;
+  }
+  virtual void CopyRawTo(std::vector<unsigned char> * dest) const {
+    dest->resize(password_.size());
+    memcpy(&dest->front(), password_.data(), password_.size());
+  }
+ private:
+  std::string password_;
+};
+
+}
+
+#endif  // _TALK_BASE_CRYPTSTRING_H_
diff --git a/talk/base/diskcache.cc b/talk/base/diskcache.cc
new file mode 100644
index 0000000..4e70543
--- /dev/null
+++ b/talk/base/diskcache.cc
@@ -0,0 +1,364 @@
+/*
+ * 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 <time.h>
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#endif
+
+#include "talk/base/common.h"
+#include "talk/base/diskcache.h"
+#include "talk/base/fileutils.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/stream.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
+
+#ifdef _DEBUG
+#define TRANSPARENT_CACHE_NAMES 1
+#else  // !_DEBUG
+#define TRANSPARENT_CACHE_NAMES 0
+#endif  // !_DEBUG
+
+namespace talk_base {
+
+class DiskCache;
+
+///////////////////////////////////////////////////////////////////////////////
+// DiskCacheAdapter
+///////////////////////////////////////////////////////////////////////////////
+
+class DiskCacheAdapter : public StreamAdapterInterface {
+public:
+  DiskCacheAdapter(const DiskCache* cache, const std::string& id, size_t index,
+                   StreamInterface* stream)
+  : StreamAdapterInterface(stream), cache_(cache), id_(id), index_(index)
+  { }
+  virtual ~DiskCacheAdapter() {
+    Close();
+    cache_->ReleaseResource(id_, index_);
+  }
+
+private:
+  const DiskCache* cache_;
+  std::string id_;
+  size_t index_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// DiskCache
+///////////////////////////////////////////////////////////////////////////////
+
+DiskCache::DiskCache() : max_cache_(0), total_size_(0), total_accessors_(0) {
+}
+
+DiskCache::~DiskCache() {
+  ASSERT(0 == total_accessors_);
+}
+
+bool DiskCache::Initialize(const std::string& folder, size_t size) {
+  if (!folder_.empty() || !Filesystem::CreateFolder(folder))
+    return false;
+
+  folder_ = folder;
+  max_cache_ = size;
+  ASSERT(0 == total_size_);
+
+  if (!InitializeEntries())
+    return false;
+
+  return CheckLimit();
+}
+
+bool DiskCache::Purge() {
+  if (folder_.empty())
+    return false;
+
+  if (total_accessors_ > 0) {
+    LOG_F(LS_WARNING) << "Cache files open";
+    return false;
+  }
+
+  if (!PurgeFiles())
+    return false;
+
+  map_.clear();
+  return true;
+}
+
+bool DiskCache::LockResource(const std::string& id) {
+  Entry* entry = GetOrCreateEntry(id, true);
+  if (LS_LOCKED == entry->lock_state)
+    return false;
+  if ((LS_UNLOCKED == entry->lock_state) && (entry->accessors > 0))
+    return false;
+  if ((total_size_ > max_cache_) && !CheckLimit()) {
+    LOG_F(LS_WARNING) << "Cache overfull";
+    return false;
+  }
+  entry->lock_state = LS_LOCKED;
+  return true;
+}
+
+StreamInterface* DiskCache::WriteResource(const std::string& id, size_t index) {
+  Entry* entry = GetOrCreateEntry(id, false);
+  if (LS_LOCKED != entry->lock_state)
+    return NULL;
+
+  size_t previous_size = 0;
+  std::string filename(IdToFilename(id, index));
+  FileStream::GetSize(filename, &previous_size);
+  ASSERT(previous_size <= entry->size);
+  if (previous_size > entry->size) {
+    previous_size = entry->size;
+  }
+
+  scoped_ptr<FileStream> file(new FileStream);
+  if (!file->Open(filename, "wb")) {
+    LOG_F(LS_ERROR) << "Couldn't create cache file";
+    return NULL;
+  }
+
+  entry->streams = stdmax(entry->streams, index + 1);
+  entry->size -= previous_size;
+  total_size_ -= previous_size;
+
+  entry->accessors += 1;
+  total_accessors_ += 1;
+  return new DiskCacheAdapter(this, id, index, file.release());
+}
+
+bool DiskCache::UnlockResource(const std::string& id) {
+  Entry* entry = GetOrCreateEntry(id, false);
+  if (LS_LOCKED != entry->lock_state)
+    return false;
+
+  if (entry->accessors > 0) {
+    entry->lock_state = LS_UNLOCKING;
+  } else {
+    entry->lock_state = LS_UNLOCKED;
+    entry->last_modified = time(0);
+    CheckLimit();
+  }
+  return true;
+}
+
+StreamInterface* DiskCache::ReadResource(const std::string& id,
+                                         size_t index) const {
+  const Entry* entry = GetEntry(id);
+  if (LS_UNLOCKED != entry->lock_state)
+    return NULL;
+  if (index >= entry->streams)
+    return NULL;
+
+  scoped_ptr<FileStream> file(new FileStream);
+  if (!file->Open(IdToFilename(id, index), "rb"))
+    return NULL;
+
+  entry->accessors += 1;
+  total_accessors_ += 1;
+  return new DiskCacheAdapter(this, id, index, file.release());
+}
+
+bool DiskCache::HasResource(const std::string& id) const {
+  const Entry* entry = GetEntry(id);
+  return (NULL != entry) && (entry->streams > 0);
+}
+
+bool DiskCache::HasResourceStream(const std::string& id, size_t index) const {
+  const Entry* entry = GetEntry(id);
+  if ((NULL == entry) || (index >= entry->streams))
+    return false;
+
+  std::string filename = IdToFilename(id, index);
+
+  return FileExists(filename);
+}
+
+bool DiskCache::DeleteResource(const std::string& id) {
+  Entry* entry = GetOrCreateEntry(id, false);
+  if (!entry)
+    return true;
+
+  if ((LS_UNLOCKED != entry->lock_state) || (entry->accessors > 0))
+    return false;
+
+  bool success = true;
+  for (size_t index = 0; index < entry->streams; ++index) {
+    std::string filename = IdToFilename(id, index);
+
+    if (!FileExists(filename))
+      continue;
+
+    if (!DeleteFile(filename)) {
+      LOG_F(LS_ERROR) << "Couldn't remove cache file: " << filename;
+      success = false;
+    }
+  }
+
+  total_size_ -= entry->size;
+  map_.erase(id);
+  return success;
+}
+
+bool DiskCache::CheckLimit() {
+#ifdef _DEBUG
+  // Temporary check to make sure everything is working correctly.
+  size_t cache_size = 0;
+  for (EntryMap::iterator it = map_.begin(); it != map_.end(); ++it) {
+    cache_size += it->second.size;
+  }
+  ASSERT(cache_size == total_size_);
+#endif  // _DEBUG
+
+  // TODO: Replace this with a non-brain-dead algorithm for clearing out the
+  // oldest resources... something that isn't O(n^2)
+  while (total_size_ > max_cache_) {
+    EntryMap::iterator oldest = map_.end();
+    for (EntryMap::iterator it = map_.begin(); it != map_.end(); ++it) {
+      if ((LS_UNLOCKED != it->second.lock_state) || (it->second.accessors > 0))
+        continue;
+      oldest = it;
+      break;
+    }
+    if (oldest == map_.end()) {
+      LOG_F(LS_WARNING) << "All resources are locked!";
+      return false;
+    }
+    for (EntryMap::iterator it = oldest++; it != map_.end(); ++it) {
+      if (it->second.last_modified < oldest->second.last_modified) {
+        oldest = it;
+      }
+    }
+    if (!DeleteResource(oldest->first)) {
+      LOG_F(LS_ERROR) << "Couldn't delete from cache!";
+      return false;
+    }
+  }
+  return true;
+}
+
+std::string DiskCache::IdToFilename(const std::string& id, size_t index) const {
+#ifdef TRANSPARENT_CACHE_NAMES
+  // This escapes colons and other filesystem characters, so the user can't open
+  // special devices (like "COM1:"), or access other directories.
+  size_t buffer_size = id.length()*3 + 1;
+  char* buffer = new char[buffer_size];
+  encode(buffer, buffer_size, id.data(), id.length(),
+         unsafe_filename_characters(), '%');
+  // TODO: ASSERT(strlen(buffer) < FileSystem::MaxBasenameLength());
+#else  // !TRANSPARENT_CACHE_NAMES
+  // We might want to just use a hash of the filename at some point, both for
+  // obfuscation, and to avoid both filename length and escaping issues.
+  ASSERT(false);
+#endif  // !TRANSPARENT_CACHE_NAMES
+
+  char extension[32];
+  sprintfn(extension, ARRAY_SIZE(extension), ".%u", index);
+
+  Pathname pathname;
+  pathname.SetFolder(folder_);
+  pathname.SetBasename(buffer);
+  pathname.SetExtension(extension);
+
+#ifdef TRANSPARENT_CACHE_NAMES
+  delete [] buffer;
+#endif  // TRANSPARENT_CACHE_NAMES
+
+  return pathname.pathname();
+}
+
+bool DiskCache::FilenameToId(const std::string& filename, std::string* id,
+                             size_t* index) const {
+  Pathname pathname(filename);
+  unsigned tempdex;
+  if (1 != sscanf(pathname.extension().c_str(), ".%u", &tempdex))
+    return false;
+
+  *index = static_cast<size_t>(tempdex);
+
+  size_t buffer_size = pathname.basename().length() + 1;
+  char* buffer = new char[buffer_size];
+  decode(buffer, buffer_size, pathname.basename().data(),
+         pathname.basename().length(), '%');
+  id->assign(buffer);
+  delete [] buffer;
+  return true;
+}
+
+DiskCache::Entry* DiskCache::GetOrCreateEntry(const std::string& id,
+                                              bool create) {
+  EntryMap::iterator it = map_.find(id);
+  if (it != map_.end())
+    return &it->second;
+  if (!create)
+    return NULL;
+  Entry e;
+  e.lock_state = LS_UNLOCKED;
+  e.accessors = 0;
+  e.size = 0;
+  e.streams = 0;
+  e.last_modified = time(0);
+  it = map_.insert(EntryMap::value_type(id, e)).first;
+  return &it->second;
+}
+
+void DiskCache::ReleaseResource(const std::string& id, size_t index) const {
+  const Entry* entry = GetEntry(id);
+  if (!entry) {
+    LOG_F(LS_WARNING) << "Missing cache entry";
+    ASSERT(false);
+    return;
+  }
+
+  entry->accessors -= 1;
+  total_accessors_ -= 1;
+
+  if (LS_UNLOCKED != entry->lock_state) {
+    // This is safe, because locked resources only issue WriteResource, which
+    // is non-const.  Think about a better way to handle it.
+    DiskCache* this2 = const_cast<DiskCache*>(this);
+    Entry* entry2 = this2->GetOrCreateEntry(id, false);
+
+    size_t new_size = 0;
+    std::string filename(IdToFilename(id, index));
+    FileStream::GetSize(filename, &new_size);
+    entry2->size += new_size;
+    this2->total_size_ += new_size;
+
+    if ((LS_UNLOCKING == entry->lock_state) && (0 == entry->accessors)) {
+      entry2->last_modified = time(0);
+      entry2->lock_state = LS_UNLOCKED;
+      this2->CheckLimit();
+    }
+  }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
diff --git a/talk/base/diskcache.h b/talk/base/diskcache.h
new file mode 100644
index 0000000..c5a1dfc
--- /dev/null
+++ b/talk/base/diskcache.h
@@ -0,0 +1,142 @@
+/*
+ * 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_BASE_DISKCACHE_H__
+#define TALK_BASE_DISKCACHE_H__
+
+#include <map>
+#include <string>
+
+#ifdef WIN32
+#undef UnlockResource
+#endif  // WIN32
+
+namespace talk_base {
+
+class StreamInterface;
+
+///////////////////////////////////////////////////////////////////////////////
+// DiskCache - An LRU cache of streams, stored on disk.
+//
+// Streams are identified by a unique resource id.  Multiple streams can be
+// associated with each resource id, distinguished by an index.  When old
+// resources are flushed from the cache, all streams associated with those
+// resources are removed together.
+// DiskCache is designed to persist across executions of the program.  It is
+// safe for use from an arbitrary number of users on a single thread, but not
+// from multiple threads or other processes.
+///////////////////////////////////////////////////////////////////////////////
+
+class DiskCache {
+public:
+  DiskCache();
+  virtual ~DiskCache();
+
+  bool Initialize(const std::string& folder, size_t size);
+  bool Purge();
+
+  bool LockResource(const std::string& id);
+  StreamInterface* WriteResource(const std::string& id, size_t index);
+  bool UnlockResource(const std::string& id);
+
+  StreamInterface* ReadResource(const std::string& id, size_t index) const;
+
+  bool HasResource(const std::string& id) const;
+  bool HasResourceStream(const std::string& id, size_t index) const;
+  bool DeleteResource(const std::string& id);
+
+ protected:
+  virtual bool InitializeEntries() = 0;
+  virtual bool PurgeFiles() = 0;
+
+  virtual bool FileExists(const std::string& filename) const = 0;
+  virtual bool DeleteFile(const std::string& filename) const = 0;
+
+  enum LockState { LS_UNLOCKED, LS_LOCKED, LS_UNLOCKING };
+  struct Entry {
+    LockState lock_state;
+    mutable size_t accessors;
+    size_t size;
+    size_t streams;
+    time_t last_modified;
+  };
+  typedef std::map<std::string, Entry> EntryMap;
+  friend class DiskCacheAdapter;
+
+  bool CheckLimit();
+
+  std::string IdToFilename(const std::string& id, size_t index) const;
+  bool FilenameToId(const std::string& filename, std::string* id,
+                    size_t* index) const;
+
+  const Entry* GetEntry(const std::string& id) const {
+    return const_cast<DiskCache*>(this)->GetOrCreateEntry(id, false);
+  }
+  Entry* GetOrCreateEntry(const std::string& id, bool create);
+
+  void ReleaseResource(const std::string& id, size_t index) const;
+
+  std::string folder_;
+  size_t max_cache_, total_size_;
+  EntryMap map_;
+  mutable size_t total_accessors_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// CacheLock - Automatically manage locking and unlocking, with optional
+// rollback semantics
+///////////////////////////////////////////////////////////////////////////////
+
+class CacheLock {
+public:
+  CacheLock(DiskCache* cache, const std::string& id, bool rollback = false)
+  : cache_(cache), id_(id), rollback_(rollback)
+  {
+    locked_ = cache_->LockResource(id_);
+  }
+  ~CacheLock() {
+    if (locked_) {
+      cache_->UnlockResource(id_);
+      if (rollback_) {
+        cache_->DeleteResource(id_);
+      }
+    }
+  }
+  bool IsLocked() const { return locked_; }
+  void Commit() { rollback_ = false; }
+
+private:
+  DiskCache* cache_;
+  std::string id_;
+  bool rollback_, locked_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif // TALK_BASE_DISKCACHE_H__
diff --git a/talk/base/event.cc b/talk/base/event.cc
new file mode 100644
index 0000000..f2fd04d
--- /dev/null
+++ b/talk/base/event.cc
@@ -0,0 +1,193 @@
+/*
+ * 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 "talk/base/event.h"
+
+#if defined(WIN32)
+#include <windows.h>
+#elif defined(POSIX)
+#include <pthread.h>
+#include <sys/time.h>
+#include <time.h>
+#else
+#error "Must define either WIN32 or POSIX."
+#endif
+
+namespace talk_base {
+
+#if defined(WIN32)
+
+Event::Event(bool manual_reset, bool initially_signaled)
+    : is_manual_reset_(manual_reset),
+      is_initially_signaled_(initially_signaled),
+      event_handle_(NULL) {
+}
+
+bool Event::EnsureInitialized() {
+  if (event_handle_ == NULL) {
+    event_handle_ = ::CreateEvent(NULL,                 // Security attributes.
+                                  is_manual_reset_,
+                                  is_initially_signaled_,
+                                  NULL);                // Name.
+  }
+
+  return (event_handle_ != NULL);
+}
+
+Event::~Event() {
+  if (event_handle_ != NULL) {
+    CloseHandle(event_handle_);
+    event_handle_ = NULL;
+  }
+}
+
+bool Event::Set() {
+  if (!EnsureInitialized())
+    return false;
+
+  SetEvent(event_handle_);
+  return true;
+}
+
+bool Event::Reset() {
+  if (!EnsureInitialized())
+    return false;
+
+  ResetEvent(event_handle_);
+  return true;
+}
+
+bool Event::Wait(int cms) {
+  DWORD ms = (cms == kForever)? INFINITE : cms;
+
+  if (!EnsureInitialized())
+    return false;
+  else
+    return (WaitForSingleObject(event_handle_, ms) == WAIT_OBJECT_0);
+}
+
+#elif defined(POSIX)
+
+Event::Event(bool manual_reset, bool initially_signaled)
+    : is_manual_reset_(manual_reset),
+      event_status_(initially_signaled),
+      event_mutex_initialized_(false),
+      event_cond_initialized_(false) {
+}
+
+bool Event::EnsureInitialized() {
+  if (!event_mutex_initialized_) {
+    if (pthread_mutex_init(&event_mutex_, NULL) == 0)
+      event_mutex_initialized_ = true;
+  }
+
+  if (!event_cond_initialized_) {
+    if (pthread_cond_init(&event_cond_, NULL) == 0)
+      event_cond_initialized_ = true;
+  }
+
+  return (event_mutex_initialized_ && event_cond_initialized_);
+}
+
+Event::~Event() {
+  if (event_mutex_initialized_) {
+    pthread_mutex_destroy(&event_mutex_);
+    event_mutex_initialized_ = false;
+  }
+
+  if (event_cond_initialized_) {
+    pthread_cond_destroy(&event_cond_);
+    event_cond_initialized_ = false;
+  }
+}
+
+bool Event::Set() {
+  if (!EnsureInitialized())
+    return false;
+
+  pthread_mutex_lock(&event_mutex_);
+  event_status_ = true;
+  pthread_cond_broadcast(&event_cond_);
+  pthread_mutex_unlock(&event_mutex_);
+  return true;
+}
+
+bool Event::Reset() {
+  if (!EnsureInitialized())
+    return false;
+
+  pthread_mutex_lock(&event_mutex_);
+  event_status_ = false;
+  pthread_mutex_unlock(&event_mutex_);
+  return true;
+}
+
+bool Event::Wait(int cms) {
+  if (!EnsureInitialized())
+    return false;
+
+  pthread_mutex_lock(&event_mutex_);
+  int error = 0;
+
+  if (cms != kForever) {
+    // Converting from seconds and microseconds (1e-6) plus
+    // milliseconds (1e-3) to seconds and nanoseconds (1e-9).
+
+    struct timeval tv;
+    gettimeofday(&tv, NULL);
+
+    struct timespec ts;
+    ts.tv_sec = tv.tv_sec + (cms / 1000);
+    ts.tv_nsec = tv.tv_usec * 1000 + (cms % 1000) * 1000000;
+
+    // Handle overflow.
+    if (ts.tv_nsec >= 1000000000) {
+      ts.tv_sec++;
+      ts.tv_nsec -= 1000000000;
+    }
+
+    while (!event_status_ && error == 0)
+      error = pthread_cond_timedwait(&event_cond_, &event_mutex_, &ts);
+  } else {
+    while (!event_status_ && error == 0)
+      error = pthread_cond_wait(&event_cond_, &event_mutex_);
+  }
+
+  // NOTE(liulk): Exactly one thread will auto-reset this event. All
+  // the other threads will think it's unsignaled.  This seems to be
+  // consistent with auto-reset events in WIN32.
+  if (error == 0 && !is_manual_reset_)
+    event_status_ = false;
+
+  pthread_mutex_unlock(&event_mutex_);
+
+  return (error == 0);
+}
+
+#endif
+
+}  // namespace talk_base
diff --git a/talk/base/event.h b/talk/base/event.h
new file mode 100644
index 0000000..757164c
--- /dev/null
+++ b/talk/base/event.h
@@ -0,0 +1,71 @@
+/*
+ * 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 TALK_BASE_EVENT_H__
+#define TALK_BASE_EVENT_H__
+
+#if defined(WIN32)
+#include "talk/base/win32.h"  // NOLINT: consider this a system header.
+#elif defined(POSIX)
+#include <pthread.h>
+#else
+#error "Must define either WIN32 or POSIX."
+#endif
+
+#include "talk/base/basictypes.h"
+
+namespace talk_base {
+
+class Event {
+ public:
+  Event(bool manual_reset, bool initially_signaled);
+  ~Event();
+
+  bool Set();
+  bool Reset();
+  bool Wait(int cms);
+
+ private:
+  bool EnsureInitialized();
+
+  bool is_manual_reset_;
+
+#if defined(WIN32)
+  bool is_initially_signaled_;
+  HANDLE event_handle_;
+#elif defined(POSIX)
+  bool event_status_;
+  bool event_mutex_initialized_;
+  pthread_mutex_t event_mutex_;
+  bool event_cond_initialized_;
+  pthread_cond_t event_cond_;
+#endif
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_EVENT_H__
diff --git a/talk/base/fileutils.cc b/talk/base/fileutils.cc
new file mode 100644
index 0000000..acfd2f8
--- /dev/null
+++ b/talk/base/fileutils.cc
@@ -0,0 +1,284 @@
+/*
+ * 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 <cassert>
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#endif
+
+#include "talk/base/pathutils.h"
+#include "talk/base/fileutils.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/stream.h"
+
+#include "talk/base/unixfilesystem.h"
+#include "talk/base/win32filesystem.h"
+
+#ifndef WIN32
+#define MAX_PATH 260
+#endif
+
+namespace talk_base {
+
+//////////////////////////
+// Directory Iterator   //
+//////////////////////////
+
+// A DirectoryIterator is created with a given directory. It originally points
+// to the first file in the directory, and can be advanecd with Next(). This
+// allows you to get information about each file.
+
+  // Constructor
+DirectoryIterator::DirectoryIterator()
+#ifdef _WIN32
+    : handle_(INVALID_HANDLE_VALUE) {
+#else
+    : dir_(NULL), dirent_(NULL) {
+#endif
+}
+
+  // Destructor
+DirectoryIterator::~DirectoryIterator() {
+#ifdef WIN32
+  if (handle_ != INVALID_HANDLE_VALUE)
+    ::FindClose(handle_);
+#else
+  if (dir_)
+    closedir(dir_);
+#endif
+}
+
+  // Starts traversing a directory.
+  // dir is the directory to traverse
+  // returns true if the directory exists and is valid
+bool DirectoryIterator::Iterate(const Pathname &dir) {
+  directory_ = dir.pathname();
+#ifdef WIN32
+  if (handle_ != INVALID_HANDLE_VALUE)
+    ::FindClose(handle_);
+  std::string d = dir.pathname() + '*';
+  handle_ = ::FindFirstFile(ToUtf16(d).c_str(), &data_);
+  if (handle_ == INVALID_HANDLE_VALUE)
+    return false;
+#else
+  if (dir_ != NULL)
+    closedir(dir_);
+  dir_ = ::opendir(directory_.c_str());
+  if (dir_ == NULL)
+    return false;
+  dirent_ = readdir(dir_);
+  if (dirent_ == NULL)
+    return false;
+
+  if (::stat(std::string(directory_ + Name()).c_str(), &stat_) != 0)
+    return false;
+#endif
+  return true;
+}
+
+  // Advances to the next file
+  // returns true if there were more files in the directory.
+bool DirectoryIterator::Next() {
+#ifdef WIN32
+  return ::FindNextFile(handle_, &data_) == TRUE;
+#else
+  dirent_ = ::readdir(dir_);
+  if (dirent_ == NULL)
+    return false;
+
+  return ::stat(std::string(directory_ + Name()).c_str(), &stat_) == 0;
+#endif
+}
+
+  // returns true if the file currently pointed to is a directory
+bool DirectoryIterator::IsDirectory() const {
+#ifdef WIN32
+  return (data_.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != FALSE;
+#else
+  return S_ISDIR(stat_.st_mode);
+#endif
+}
+
+  // returns the name of the file currently pointed to
+std::string DirectoryIterator::Name() const {
+#ifdef WIN32
+  return ToUtf8(data_.cFileName);
+#else
+  assert(dirent_ != NULL);
+  return dirent_->d_name;
+#endif
+}
+
+  // returns the size of the file currently pointed to
+size_t DirectoryIterator::FileSize() const {
+#ifndef WIN32
+  return stat_.st_size;
+#else
+  return data_.nFileSizeLow;
+#endif
+}
+
+  // returns the last modified time of this file
+time_t DirectoryIterator::FileModifyTime() const {
+#ifdef WIN32
+  time_t val;
+  FileTimeToUnixTime(data_.ftLastWriteTime, &val);
+  return val;
+#else
+  return stat_.st_mtime;
+#endif
+}
+
+scoped_ptr<FilesystemInterface> Filesystem::default_filesystem_;
+FilesystemInterface *Filesystem::EnsureDefaultFilesystem() {
+  if (!default_filesystem_.get())
+#ifdef WIN32
+    default_filesystem_.reset(new Win32Filesystem());
+#else
+    default_filesystem_.reset(new UnixFilesystem());
+#endif
+    return default_filesystem_.get();
+}
+
+bool FilesystemInterface::CopyFolder(const Pathname &old_path,
+                                     const Pathname &new_path) {
+  VERIFY(IsFolder(old_path));
+  Pathname new_dir;
+  new_dir.SetFolder(new_path.pathname());
+  Pathname old_dir;
+  old_dir.SetFolder(old_path.pathname());
+  if (!CreateFolder(new_dir))
+    return false;
+  DirectoryIterator di;
+  di.Iterate(old_dir.pathname());
+  while (di.Next()) {
+    if (di.Name() == "." || di.Name() == "..")
+      continue;
+    Pathname source;
+    Pathname dest;
+    source.SetFolder(old_dir.pathname());
+    dest.SetFolder(new_path.pathname());
+    source.SetFilename(di.Name());
+    dest.SetFilename(di.Name());
+    if (!CopyFileOrFolder(source, dest))
+      return false;
+  }
+  return true;
+}
+
+bool FilesystemInterface::DeleteFolderContents(const Pathname &folder) {
+  bool success = true;
+  VERIFY(IsFolder(folder));
+  DirectoryIterator *di = IterateDirectory();
+  di->Iterate(folder);
+  while (di->Next()) {
+    if (di->Name() == "." || di->Name() == "..")
+      continue;
+    Pathname subdir;
+    subdir.SetFolder(folder.pathname());
+    if (di->IsDirectory()) {
+      subdir.AppendFolder(di->Name());
+      if (!DeleteFolderAndContents(subdir)) {
+        success = false;
+      }
+    } else {
+      subdir.SetFilename(di->Name());
+      if (!DeleteFile(subdir)) {
+        success = false;
+      }
+    }
+  }
+  delete di;
+  return success;
+}
+
+bool FilesystemInterface::CleanAppTempFolder() {
+  Pathname path;
+  if (!GetAppTempFolder(&path))
+    return false;
+  if (IsAbsent(path))
+    return true;
+  if (!IsTemporaryPath(path)) {
+    ASSERT(false);
+    return false;
+  }
+  return DeleteFolderContents(path);
+}
+
+Pathname Filesystem::GetCurrentDirectory() {
+  return EnsureDefaultFilesystem()->GetCurrentDirectory();
+}
+
+bool CreateUniqueFile(Pathname& path, bool create_empty) {
+  LOG(LS_INFO) << "Path " << path.pathname() << std::endl;
+  // If no folder is supplied, use the temporary folder
+  if (path.folder().empty()) {
+    Pathname temporary_path;
+    if (!Filesystem::GetTemporaryFolder(temporary_path, true, NULL)) {
+      printf("Get temp failed\n");
+      return false;
+    }
+    path.SetFolder(temporary_path.pathname());
+  }
+
+  // If no filename is supplied, use a temporary name
+  if (path.filename().empty()) {
+    std::string folder(path.folder());
+    std::string filename = Filesystem::TempFilename(folder, "gt");
+    path.SetPathname(filename);
+    if (!create_empty) {
+      Filesystem::DeleteFile(path.pathname());
+    }
+    return true;
+  }
+
+  // Otherwise, create a unique name based on the given filename
+  // foo.txt -> foo-N.txt
+  const std::string basename = path.basename();
+  const size_t MAX_VERSION = 100;
+  size_t version = 0;
+  while (version < MAX_VERSION) {
+    std::string pathname = path.pathname();
+
+    if (!Filesystem::IsFile(pathname)) {
+      if (create_empty) {
+        FileStream* fs = Filesystem::OpenFile(pathname, "w");
+        delete fs;
+      }
+      return true;
+    }
+    version += 1;
+    char version_base[MAX_PATH];
+    sprintfn(version_base, ARRAY_SIZE(version_base), "%s-%u",
+             basename.c_str(), version);
+    path.SetBasename(version_base);
+  }
+  return true;
+}
+
+}  // namespace talk_base
diff --git a/talk/base/fileutils.h b/talk/base/fileutils.h
new file mode 100644
index 0000000..217cd83
--- /dev/null
+++ b/talk/base/fileutils.h
@@ -0,0 +1,456 @@
+/*
+ * 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_BASE_FILEUTILS_H_
+#define TALK_BASE_FILEUTILS_H_
+
+#include <string>
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#else
+#include <sys/types.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#endif
+
+#include "talk/base/basictypes.h"
+#include "talk/base/common.h"
+#include "talk/base/scoped_ptr.h"
+
+namespace talk_base {
+
+class FileStream;
+class Pathname;
+
+//////////////////////////
+// Directory Iterator   //
+//////////////////////////
+
+// A DirectoryIterator is created with a given directory. It originally points
+// to the first file in the directory, and can be advanecd with Next(). This
+// allows you to get information about each file.
+
+class DirectoryIterator {
+  friend class Filesystem;
+ public:
+  // Constructor
+  DirectoryIterator();
+  // Destructor
+  virtual ~DirectoryIterator();
+
+  // Starts traversing a directory
+  // dir is the directory to traverse
+  // returns true if the directory exists and is valid
+  // The iterator will point to the first entry in the directory
+  virtual bool Iterate(const Pathname &path);
+
+  // Advances to the next file
+  // returns true if there were more files in the directory.
+  virtual bool Next();
+
+  // returns true if the file currently pointed to is a directory
+  virtual bool IsDirectory() const;
+
+  // returns the name of the file currently pointed to
+  virtual std::string Name() const;
+
+  // returns the size of the file currently pointed to
+  virtual size_t FileSize() const;
+
+  // returns the last modified time of the file currently pointed to
+  virtual time_t FileModifyTime() const;
+
+  // checks whether current file is a special directory file "." or ".."
+  bool IsDots() const {
+    std::string filename(Name());
+    return (filename.compare(".") == 0) || (filename.compare("..") == 0);
+  }
+
+ private:
+  std::string directory_;
+#ifdef WIN32
+  WIN32_FIND_DATA data_;
+  HANDLE handle_;
+#else
+  DIR *dir_;
+  struct dirent *dirent_;
+  struct stat stat_;
+#endif
+};
+
+enum FileTimeType { FTT_CREATED, FTT_MODIFIED, FTT_ACCESSED };
+
+class FilesystemInterface {
+ public:
+  virtual ~FilesystemInterface() {}
+
+  // Returns a DirectoryIterator for a given pathname.
+  // TODO: Do fancy abstracted stuff
+  virtual DirectoryIterator *IterateDirectory() {
+    return new DirectoryIterator();
+  }
+
+  // Opens a file. Returns an open StreamInterface if function succeeds.
+  // Otherwise, returns NULL.
+  virtual FileStream *OpenFile(const Pathname &filename,
+                               const std::string &mode) = 0;
+
+  // Atomically creates an empty file accessible only to the current user if one
+  // does not already exist at the given path, otherwise fails. This is the only
+  // secure way to create a file in a shared temp directory (e.g., C:\Temp on
+  // Windows or /tmp on Linux).
+  // Note that if it is essential that a file be successfully created then the
+  // app must generate random names and retry on failure, or else it will be
+  // vulnerable to a trivial DoS.
+  virtual bool CreatePrivateFile(const Pathname &filename) = 0;
+
+  // This will attempt to delete the path located at filename.
+  // It ASSERTS and returns false if the path points to a folder or a
+  // non-existent file.
+  virtual bool DeleteFile(const Pathname &filename) = 0;
+
+  // This will attempt to delete the empty folder located at 'folder'
+  // It ASSERTS and returns false if the path points to a file or a non-existent
+  // folder. It fails normally if the folder is not empty or can otherwise
+  // not be deleted.
+  virtual bool DeleteEmptyFolder(const Pathname &folder) = 0;
+
+  // This will call IterateDirectory, to get a directory iterator, and then
+  // call DeleteFolderAndContents and DeleteFile on every path contained in this
+  // folder. If the folder is empty, this returns true.
+  virtual bool DeleteFolderContents(const Pathname &folder);
+
+  // This deletes the contents of a folder, recursively, and then deletes
+  // the folder itself.
+  virtual bool DeleteFolderAndContents(const Pathname &folder) {
+    return DeleteFolderContents(folder) && DeleteEmptyFolder(folder);
+  }
+
+  // This will delete whatever is located at path, be it a file or a folder.
+  // If it is a folder, it will delete it recursively by calling
+  // DeleteFolderAndContents
+  bool DeleteFileOrFolder(const Pathname &path) {
+    if (IsFolder(path))
+      return DeleteFolderAndContents(path);
+    else
+      return DeleteFile(path);
+  }
+
+  // Creates a directory. This will call itself recursively to create /foo/bar
+  // even if /foo does not exist. Returns true if the function succeeds.
+  virtual bool CreateFolder(const Pathname &pathname) = 0;
+
+  // This moves a file from old_path to new_path, where "old_path" is a
+  // plain file. This ASSERTs and returns false if old_path points to a
+  // directory, and returns true if the function succeeds.
+  // If the new path is on a different volume than the old path, this function
+  // will attempt to copy and, if that succeeds, delete the old path.
+  virtual bool MoveFolder(const Pathname &old_path,
+                          const Pathname &new_path) = 0;
+
+  // This moves a directory from old_path to new_path, where "old_path" is a
+  // directory. This ASSERTs and returns false if old_path points to a plain
+  // file, and returns true if the function succeeds.
+  // If the new path is on a different volume, this function will attempt to
+  // copy and if that succeeds, delete the old path.
+  virtual bool MoveFile(const Pathname &old_path, const Pathname &new_path) = 0;
+
+  // This attempts to move whatever is located at old_path to new_path,
+  // be it a file or folder.
+  bool MoveFileOrFolder(const Pathname &old_path, const Pathname &new_path) {
+    if (IsFile(old_path)) {
+      return MoveFile(old_path, new_path);
+    } else {
+      return MoveFolder(old_path, new_path);
+    }
+  }
+
+  // This copies a file from old_path to new_path. This method ASSERTs and
+  // returns false if old_path is a folder, and returns true if the copy
+  // succeeds.
+  virtual bool CopyFile(const Pathname &old_path, const Pathname &new_path) = 0;
+
+  // This copies a folder from old_path to new_path.
+  bool CopyFolder(const Pathname &old_path, const Pathname &new_path);
+
+  bool CopyFileOrFolder(const Pathname &old_path, const Pathname &new_path) {
+    if (IsFile(old_path))
+      return CopyFile(old_path, new_path);
+    else
+      return CopyFolder(old_path, new_path);
+  }
+
+  // Returns true if pathname refers to a directory
+  virtual bool IsFolder(const Pathname& pathname) = 0;
+
+  // Returns true if pathname refers to a file
+  virtual bool IsFile(const Pathname& pathname) = 0;
+
+  // Returns true if pathname refers to no filesystem object, every parent
+  // directory either exists, or is also absent.
+  virtual bool IsAbsent(const Pathname& pathname) = 0;
+
+  // Returns true if pathname represents a temporary location on the system.
+  virtual bool IsTemporaryPath(const Pathname& pathname) = 0;
+
+  // A folder appropriate for storing temporary files (Contents are
+  // automatically deleted when the program exits)
+  virtual bool GetTemporaryFolder(Pathname &path, bool create,
+                                  const std::string *append) = 0;
+
+  virtual std::string TempFilename(const Pathname &dir,
+                                   const std::string &prefix) = 0;
+
+  // Determines the size of the file indicated by path.
+  virtual bool GetFileSize(const Pathname& path, size_t* size) = 0;
+
+  // Determines a timestamp associated with the file indicated by path.
+  virtual bool GetFileTime(const Pathname& path, FileTimeType which,
+                           time_t* time) = 0;
+
+  // Returns the path to the running application.
+  // Note: This is not guaranteed to work on all platforms.  Be aware of the
+  // limitations before using it, and robustly handle failure.
+  virtual bool GetAppPathname(Pathname* path) = 0;
+
+  // Get a folder that is unique to the current application, which is suitable
+  // for sharing data between executions of the app.  If the per_user arg is
+  // true, the folder is also specific to the current user.
+  virtual bool GetAppDataFolder(Pathname* path, bool per_user) = 0;
+
+  // Get a temporary folder that is unique to the current user and application.
+  // TODO: Re-evaluate the goals of this function.  We probably just need any
+  // directory that won't collide with another existing directory, and which
+  // will be cleaned up when the program exits.
+  virtual bool GetAppTempFolder(Pathname* path) = 0;
+
+  // Delete the contents of the folder returned by GetAppTempFolder
+  bool CleanAppTempFolder();
+
+  virtual bool GetDiskFreeSpace(const Pathname& path, int64 *freebytes) = 0;
+
+  // Returns the absolute path of the current directory.
+  virtual Pathname GetCurrentDirectory() = 0;
+
+  // Note: These might go into some shared config section later, but they're
+  // used by some methods in this interface, so we're leaving them here for now.
+  void SetOrganizationName(const std::string& organization) {
+    organization_name_ = organization;
+  }
+  void GetOrganizationName(std::string* organization) {
+    ASSERT(NULL != organization);
+    *organization = organization_name_;
+  }
+  void SetApplicationName(const std::string& application) {
+    application_name_ = application;
+  }
+  void GetApplicationName(std::string* application) {
+    ASSERT(NULL != application);
+    *application = application_name_;
+  }
+
+ protected:
+  std::string organization_name_;
+  std::string application_name_;
+};
+
+class Filesystem {
+ public:
+  static FilesystemInterface *default_filesystem() {
+    ASSERT(default_filesystem_.get() != NULL);
+    return default_filesystem_.get();
+  }
+
+  static void set_default_filesystem(FilesystemInterface *filesystem) {
+    default_filesystem_.reset(filesystem);
+  }
+
+  static FilesystemInterface *swap_default_filesystem(
+      FilesystemInterface *filesystem) {
+    FilesystemInterface *cur = default_filesystem_.release();
+    default_filesystem_.reset(filesystem);
+    return cur;
+  }
+
+  static DirectoryIterator *IterateDirectory() {
+    return EnsureDefaultFilesystem()->IterateDirectory();
+  }
+
+  static bool CreateFolder(const Pathname &pathname) {
+    return EnsureDefaultFilesystem()->CreateFolder(pathname);
+  }
+
+  static FileStream *OpenFile(const Pathname &filename,
+                              const std::string &mode) {
+    return EnsureDefaultFilesystem()->OpenFile(filename, mode);
+  }
+
+  static bool CreatePrivateFile(const Pathname &filename) {
+    return EnsureDefaultFilesystem()->CreatePrivateFile(filename);
+  }
+
+  static bool DeleteFile(const Pathname &filename) {
+    return EnsureDefaultFilesystem()->DeleteFile(filename);
+  }
+
+  static bool DeleteEmptyFolder(const Pathname &folder) {
+    return EnsureDefaultFilesystem()->DeleteEmptyFolder(folder);
+  }
+
+  static bool DeleteFolderContents(const Pathname &folder) {
+    return EnsureDefaultFilesystem()->DeleteFolderContents(folder);
+  }
+
+  static bool DeleteFolderAndContents(const Pathname &folder) {
+    return EnsureDefaultFilesystem()->DeleteFolderAndContents(folder);
+  }
+
+  static bool MoveFolder(const Pathname &old_path, const Pathname &new_path) {
+    return EnsureDefaultFilesystem()->MoveFolder(old_path, new_path);
+  }
+
+  static bool MoveFile(const Pathname &old_path, const Pathname &new_path) {
+    return EnsureDefaultFilesystem()->MoveFile(old_path, new_path);
+  }
+
+  static bool CopyFolder(const Pathname &old_path, const Pathname &new_path) {
+    return EnsureDefaultFilesystem()->CopyFolder(old_path, new_path);
+  }
+
+  static bool CopyFile(const Pathname &old_path, const Pathname &new_path) {
+    return EnsureDefaultFilesystem()->CopyFile(old_path, new_path);
+  }
+
+  static bool IsFolder(const Pathname& pathname) {
+    return EnsureDefaultFilesystem()->IsFolder(pathname);
+  }
+
+  static bool IsFile(const Pathname &pathname) {
+    return EnsureDefaultFilesystem()->IsFile(pathname);
+  }
+
+  static bool IsAbsent(const Pathname &pathname) {
+    return EnsureDefaultFilesystem()->IsAbsent(pathname);
+  }
+
+  static bool IsTemporaryPath(const Pathname& pathname) {
+    return EnsureDefaultFilesystem()->IsTemporaryPath(pathname);
+  }
+
+  static bool GetTemporaryFolder(Pathname &path, bool create,
+                                 const std::string *append) {
+    return EnsureDefaultFilesystem()->GetTemporaryFolder(path, create, append);
+  }
+
+  static std::string TempFilename(const Pathname &dir,
+                                  const std::string &prefix) {
+    return EnsureDefaultFilesystem()->TempFilename(dir, prefix);
+  }
+
+  static bool GetFileSize(const Pathname& path, size_t* size) {
+    return EnsureDefaultFilesystem()->GetFileSize(path, size);
+  }
+
+  static bool GetFileTime(const Pathname& path, FileTimeType which,
+                          time_t* time) {
+    return EnsureDefaultFilesystem()->GetFileTime(path, which, time);
+  }
+
+  static bool GetAppPathname(Pathname* path) {
+    return EnsureDefaultFilesystem()->GetAppPathname(path);
+  }
+
+  static bool GetAppDataFolder(Pathname* path, bool per_user) {
+    return EnsureDefaultFilesystem()->GetAppDataFolder(path, per_user);
+  }
+
+  static bool GetAppTempFolder(Pathname* path) {
+    return EnsureDefaultFilesystem()->GetAppTempFolder(path);
+  }
+
+  static bool CleanAppTempFolder() {
+    return EnsureDefaultFilesystem()->CleanAppTempFolder();
+  }
+
+  static bool GetDiskFreeSpace(const Pathname& path, int64 *freebytes) {
+    return EnsureDefaultFilesystem()->GetDiskFreeSpace(path, freebytes);
+  }
+
+  // Definition has to be in the .cc file due to returning forward-declared
+  // Pathname by value.
+  static Pathname GetCurrentDirectory();
+
+  static void SetOrganizationName(const std::string& organization) {
+    EnsureDefaultFilesystem()->SetOrganizationName(organization);
+  }
+
+  static void GetOrganizationName(std::string* organization) {
+    EnsureDefaultFilesystem()->GetOrganizationName(organization);
+  }
+
+  static void SetApplicationName(const std::string& application) {
+    EnsureDefaultFilesystem()->SetApplicationName(application);
+  }
+
+  static void GetApplicationName(std::string* application) {
+    EnsureDefaultFilesystem()->GetApplicationName(application);
+  }
+
+ private:
+  static scoped_ptr<FilesystemInterface> default_filesystem_;
+
+  static FilesystemInterface *EnsureDefaultFilesystem();
+  DISALLOW_IMPLICIT_CONSTRUCTORS(Filesystem);
+};
+
+class FilesystemScope{
+ public:
+  explicit FilesystemScope(FilesystemInterface *new_fs) {
+    old_fs_ = Filesystem::swap_default_filesystem(new_fs);
+  }
+  ~FilesystemScope() {
+    Filesystem::set_default_filesystem(old_fs_);
+  }
+ private:
+  FilesystemInterface* old_fs_;
+  DISALLOW_IMPLICIT_CONSTRUCTORS(FilesystemScope);
+};
+
+// Generates a unique filename based on the input path.  If no path component
+// is specified, it uses the temporary directory.  If a filename is provided,
+// up to 100 variations of form basename-N.extension are tried.  When
+// create_empty is true, an empty file of this name is created (which
+// decreases the chance of a temporary filename collision with another
+// process).
+bool CreateUniqueFile(Pathname& path, bool create_empty);
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_FILEUTILS_H_
+
diff --git a/talk/base/firewallsocketserver.cc b/talk/base/firewallsocketserver.cc
new file mode 100644
index 0000000..a99c72e
--- /dev/null
+++ b/talk/base/firewallsocketserver.cc
@@ -0,0 +1,246 @@
+/*
+ * 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/base/firewallsocketserver.h"
+
+#include <cassert>
+#include <algorithm>
+
+#include "talk/base/asyncsocket.h"
+#include "talk/base/logging.h"
+
+namespace talk_base {
+
+class FirewallSocket : public AsyncSocketAdapter {
+ public:
+  FirewallSocket(FirewallSocketServer* server, AsyncSocket* socket, int type)
+    : AsyncSocketAdapter(socket), server_(server), type_(type) {
+  }
+
+  virtual int Connect(const SocketAddress& addr) {
+    if (type_ == SOCK_STREAM) {
+      if (!server_->Check(FP_TCP, GetLocalAddress(), addr)) {
+        LOG(LS_VERBOSE) << "FirewallSocket outbound TCP connection from "
+                        << GetLocalAddress().ToString() << " to "
+                        << addr.ToString() << " denied";
+        // TODO: Handle this asynchronously.
+        SetError(EHOSTUNREACH);
+        return SOCKET_ERROR;
+      }
+    }
+    return AsyncSocketAdapter::Connect(addr);
+  }
+  virtual int Send(const void* pv, size_t cb) {
+    return SendTo(pv, cb, GetRemoteAddress());
+  }
+  virtual int SendTo(const void* pv, size_t cb, const SocketAddress& addr) {
+    if (type_ == SOCK_DGRAM) {
+      if (!server_->Check(FP_UDP, GetLocalAddress(), addr)) {
+        LOG(LS_VERBOSE) << "FirewallSocket outbound UDP packet from "
+                        << GetLocalAddress().ToString() << " to "
+                        << addr.ToString() << " dropped";
+        return static_cast<int>(cb);
+      }
+    }
+    return AsyncSocketAdapter::SendTo(pv, cb, addr);
+  }
+  virtual int Recv(void* pv, size_t cb) {
+    SocketAddress addr;
+    return RecvFrom(pv, cb, &addr);
+  }
+  virtual int RecvFrom(void* pv, size_t cb, SocketAddress* paddr) {
+    if (type_ == SOCK_DGRAM) {
+      while (true) {
+        int res = AsyncSocketAdapter::RecvFrom(pv, cb, paddr);
+        if (res <= 0)
+          return res;
+        if (server_->Check(FP_UDP, *paddr, GetLocalAddress()))
+          return res;
+        LOG(LS_VERBOSE) << "FirewallSocket inbound UDP packet from "
+                        << paddr->ToString() << " to "
+                        << GetLocalAddress().ToString() << " dropped";
+      }
+    }
+    return AsyncSocketAdapter::RecvFrom(pv, cb, paddr);
+  }
+
+  virtual int Listen(int backlog) {
+    if (!server_->tcp_listen_enabled()) {
+      LOG(LS_VERBOSE) << "FirewallSocket listen attempt denied";
+      return -1;
+    }
+
+    return AsyncSocketAdapter::Listen(backlog);
+  }
+  virtual AsyncSocket* Accept(SocketAddress* paddr) {
+    SocketAddress addr;
+    while (AsyncSocket* sock = AsyncSocketAdapter::Accept(&addr)) {
+      if (server_->Check(FP_TCP, addr, GetLocalAddress())) {
+        if (paddr)
+          *paddr = addr;
+        return sock;
+      }
+      sock->Close();
+      delete sock;
+      LOG(LS_VERBOSE) << "FirewallSocket inbound TCP connection from "
+                      << addr.ToString() << " to "
+                      << GetLocalAddress().ToString() << " denied";
+    }
+    return 0;
+  }
+
+ private:
+  FirewallSocketServer* server_;
+  int type_;
+};
+
+FirewallSocketServer::FirewallSocketServer(SocketServer* server,
+                                           FirewallManager* manager,
+                                           bool should_delete_server)
+    : server_(server), manager_(manager),
+      should_delete_server_(should_delete_server),
+      udp_sockets_enabled_(true), tcp_sockets_enabled_(true),
+      tcp_listen_enabled_(true) {
+  if (manager_)
+    manager_->AddServer(this);
+}
+
+FirewallSocketServer::~FirewallSocketServer() {
+  if (manager_)
+    manager_->RemoveServer(this);
+
+  if (server_ && should_delete_server_) {
+    delete server_;
+    server_ = NULL;
+  }
+}
+
+void FirewallSocketServer::AddRule(bool allow, FirewallProtocol p,
+                                   FirewallDirection d,
+                                   const SocketAddress& addr) {
+  SocketAddress src, dst;
+  if (d == FD_IN) {
+    dst = addr;
+  } else {
+    src = addr;
+  }
+  AddRule(allow, p, src, dst);
+}
+
+
+void FirewallSocketServer::AddRule(bool allow, FirewallProtocol p,
+                                   const SocketAddress& src,
+                                   const SocketAddress& dst) {
+  Rule r;
+  r.allow = allow;
+  r.p = p;
+  r.src = src;
+  r.dst = dst;
+  CritScope scope(&crit_);
+  rules_.push_back(r);
+}
+
+void FirewallSocketServer::ClearRules() {
+  CritScope scope(&crit_);
+  rules_.clear();
+}
+
+bool FirewallSocketServer::Check(FirewallProtocol p,
+                                 const SocketAddress& src,
+                                 const SocketAddress& dst) {
+  CritScope scope(&crit_);
+  for (size_t i = 0; i < rules_.size(); ++i) {
+    const Rule& r = rules_[i];
+    if ((r.p != p) && (r.p != FP_ANY))
+      continue;
+    if ((r.src.ip() != src.ip()) && !r.src.IsAny())
+      continue;
+    if ((r.src.port() != src.port()) && (r.src.port() != 0))
+      continue;
+    if ((r.dst.ip() != dst.ip()) && !r.dst.IsAny())
+      continue;
+    if ((r.dst.port() != dst.port()) && (r.dst.port() != 0))
+      continue;
+    return r.allow;
+  }
+  return true;
+}
+
+Socket* FirewallSocketServer::CreateSocket(int type) {
+  return WrapSocket(server_->CreateAsyncSocket(type), type);
+}
+
+AsyncSocket* FirewallSocketServer::CreateAsyncSocket(int type) {
+  return WrapSocket(server_->CreateAsyncSocket(type), type);
+}
+
+AsyncSocket* FirewallSocketServer::WrapSocket(AsyncSocket* sock, int type) {
+  if (!sock ||
+      (type == SOCK_STREAM && !tcp_sockets_enabled_) ||
+      (type == SOCK_DGRAM && !udp_sockets_enabled_)) {
+    LOG(LS_VERBOSE) << "FirewallSocketServer socket creation denied";
+    return NULL;
+  }
+  return new FirewallSocket(this, sock, type);
+}
+
+FirewallManager::FirewallManager() {
+}
+
+FirewallManager::~FirewallManager() {
+  assert(servers_.empty());
+}
+
+void FirewallManager::AddServer(FirewallSocketServer* server) {
+  CritScope scope(&crit_);
+  servers_.push_back(server);
+}
+
+void FirewallManager::RemoveServer(FirewallSocketServer* server) {
+  CritScope scope(&crit_);
+  servers_.erase(std::remove(servers_.begin(), servers_.end(), server),
+                 servers_.end());
+}
+
+void FirewallManager::AddRule(bool allow, FirewallProtocol p,
+                              FirewallDirection d, const SocketAddress& addr) {
+  CritScope scope(&crit_);
+  for (std::vector<FirewallSocketServer*>::const_iterator it =
+      servers_.begin(); it != servers_.end(); ++it) {
+    (*it)->AddRule(allow, p, d, addr);
+  }
+}
+
+void FirewallManager::ClearRules() {
+  CritScope scope(&crit_);
+  for (std::vector<FirewallSocketServer*>::const_iterator it =
+      servers_.begin(); it != servers_.end(); ++it) {
+    (*it)->ClearRules();
+  }
+}
+
+}  // namespace talk_base
diff --git a/talk/base/firewallsocketserver.h b/talk/base/firewallsocketserver.h
new file mode 100644
index 0000000..94ba2d2
--- /dev/null
+++ b/talk/base/firewallsocketserver.h
@@ -0,0 +1,133 @@
+/*
+ * 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_BASE_FIREWALLSOCKETSERVER_H_
+#define TALK_BASE_FIREWALLSOCKETSERVER_H_
+
+#include <vector>
+#include "talk/base/socketserver.h"
+#include "talk/base/criticalsection.h"
+
+namespace talk_base {
+
+class FirewallManager;
+
+// This SocketServer shim simulates a rule-based firewall server.
+
+enum FirewallProtocol { FP_UDP, FP_TCP, FP_ANY };
+enum FirewallDirection { FD_IN, FD_OUT, FD_ANY };
+
+class FirewallSocketServer : public SocketServer {
+ public:
+  FirewallSocketServer(SocketServer * server,
+                       FirewallManager * manager = NULL,
+                       bool should_delete_server = false);
+  virtual ~FirewallSocketServer();
+
+  SocketServer* socketserver() const { return server_; }
+  void set_socketserver(SocketServer* server) {
+    if (server_ && should_delete_server_) {
+      delete server_;
+      server_ = NULL;
+      should_delete_server_ = false;
+    }
+    server_ = server;
+  }
+
+  // Settings to control whether CreateSocket or Socket::Listen succeed.
+  void set_udp_sockets_enabled(bool enabled) { udp_sockets_enabled_ = enabled; }
+  void set_tcp_sockets_enabled(bool enabled) { tcp_sockets_enabled_ = enabled; }
+  bool tcp_listen_enabled() const { return tcp_listen_enabled_; }
+  void set_tcp_listen_enabled(bool enabled) { tcp_listen_enabled_ = enabled; }
+
+  // Rules govern the behavior of Connect/Accept/Send/Recv attempts.
+  void AddRule(bool allow, FirewallProtocol p = FP_ANY,
+               FirewallDirection d = FD_ANY,
+               const SocketAddress& addr = SocketAddress());
+  void AddRule(bool allow, FirewallProtocol p,
+               const SocketAddress& src, const SocketAddress& dst);
+  void ClearRules();
+
+  bool Check(FirewallProtocol p,
+             const SocketAddress& src, const SocketAddress& dst);
+
+  virtual Socket* CreateSocket(int type);
+  virtual AsyncSocket* CreateAsyncSocket(int type);
+  virtual void SetMessageQueue(MessageQueue* queue) {
+    server_->SetMessageQueue(queue);
+  }
+  virtual bool Wait(int cms, bool process_io) {
+    return server_->Wait(cms, process_io);
+  }
+  virtual void WakeUp() {
+    return server_->WakeUp();
+  }
+
+  Socket * WrapSocket(Socket * sock, int type);
+  AsyncSocket * WrapSocket(AsyncSocket * sock, int type);
+
+ private:
+  SocketServer * server_;
+  FirewallManager * manager_;
+  CriticalSection crit_;
+  struct Rule {
+    bool allow;
+    FirewallProtocol p;
+    FirewallDirection d;
+    SocketAddress src;
+    SocketAddress dst;
+  };
+  std::vector<Rule> rules_;
+  bool should_delete_server_;
+  bool udp_sockets_enabled_;
+  bool tcp_sockets_enabled_;
+  bool tcp_listen_enabled_;
+};
+
+// FirewallManager allows you to manage firewalls in multiple threads together
+
+class FirewallManager {
+ public:
+  FirewallManager();
+  ~FirewallManager();
+
+  void AddServer(FirewallSocketServer * server);
+  void RemoveServer(FirewallSocketServer * server);
+
+  void AddRule(bool allow, FirewallProtocol p = FP_ANY,
+               FirewallDirection d = FD_ANY,
+               const SocketAddress& addr = SocketAddress());
+  void ClearRules();
+
+ private:
+  CriticalSection crit_;
+  std::vector<FirewallSocketServer *> servers_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_FIREWALLSOCKETSERVER_H_
diff --git a/talk/base/flags.cc b/talk/base/flags.cc
new file mode 100644
index 0000000..09a8edf
--- /dev/null
+++ b/talk/base/flags.cc
@@ -0,0 +1,324 @@
+/*
+ * libjingle
+ * Copyright 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#include <shellapi.h>
+#endif
+
+#include "talk/base/flags.h"
+
+
+// -----------------------------------------------------------------------------
+// Implementation of Flag
+
+Flag::Flag(const char* file, const char* name, const char* comment,
+           Type type, void* variable, FlagValue default__)
+    : file_(file),
+      name_(name),
+      comment_(comment),
+      type_(type),
+      variable_(reinterpret_cast<FlagValue*>(variable)),
+      default_(default__) {
+  FlagList::Register(this);
+}
+
+
+void Flag::SetToDefault() {
+  // Note that we cannot simply do '*variable_ = default_;' since
+  // flag variables are not really of type FlagValue and thus may
+  // be smaller! The FlagValue union is simply 'overlayed' on top
+  // of a flag variable for convenient access. Since union members
+  // are guarantee to be aligned at the beginning, this works.
+  switch (type_) {
+    case Flag::BOOL:
+      variable_->b = default_.b;
+      return;
+    case Flag::INT:
+      variable_->i = default_.i;
+      return;
+    case Flag::FLOAT:
+      variable_->f = default_.f;
+      return;
+    case Flag::STRING:
+      variable_->s = default_.s;
+      return;
+  }
+  UNREACHABLE();
+}
+
+
+static const char* Type2String(Flag::Type type) {
+  switch (type) {
+    case Flag::BOOL: return "bool";
+    case Flag::INT: return "int";
+    case Flag::FLOAT: return "float";
+    case Flag::STRING: return "string";
+  }
+  UNREACHABLE();
+  return NULL;
+}
+
+
+static void PrintFlagValue(Flag::Type type, FlagValue* p) {
+  switch (type) {
+    case Flag::BOOL:
+      printf("%s", (p->b ? "true" : "false"));
+      return;
+    case Flag::INT:
+      printf("%d", p->i);
+      return;
+    case Flag::FLOAT:
+      printf("%f", p->f);
+      return;
+    case Flag::STRING:
+      printf("%s", p->s);
+      return;
+  }
+  UNREACHABLE();
+}
+
+
+void Flag::Print(bool print_current_value) {
+  printf("  --%s (%s)  type: %s  default: ", name_, comment_,
+          Type2String(type_));
+  PrintFlagValue(type_, &default_);
+  if (print_current_value) {
+    printf("  current value: ");
+    PrintFlagValue(type_, variable_);
+  }
+  printf("\n");
+}
+
+
+// -----------------------------------------------------------------------------
+// Implementation of FlagList
+
+Flag* FlagList::list_ = NULL;
+
+
+FlagList::FlagList() {
+  list_ = NULL;
+}
+
+void FlagList::Print(const char* file, bool print_current_value) {
+  // Since flag registration is likely by file (= C++ file),
+  // we don't need to sort by file and still get grouped output.
+  const char* current = NULL;
+  for (Flag* f = list_; f != NULL; f = f->next()) {
+    if (file == NULL || file == f->file()) {
+      if (current != f->file()) {
+        printf("Flags from %s:\n", f->file());
+        current = f->file();
+      }
+      f->Print(print_current_value);
+    }
+  }
+}
+
+
+Flag* FlagList::Lookup(const char* name) {
+  Flag* f = list_;
+  while (f != NULL && strcmp(name, f->name()) != 0)
+    f = f->next();
+  return f;
+}
+
+
+void FlagList::SplitArgument(const char* arg,
+                             char* buffer, int buffer_size,
+                             const char** name, const char** value,
+                             bool* is_bool) {
+  *name = NULL;
+  *value = NULL;
+  *is_bool = false;
+
+  if (*arg == '-') {
+    // find the begin of the flag name
+    arg++;  // remove 1st '-'
+    if (*arg == '-')
+      arg++;  // remove 2nd '-'
+    if (arg[0] == 'n' && arg[1] == 'o') {
+      arg += 2;  // remove "no"
+      *is_bool = true;
+    }
+    *name = arg;
+
+    // find the end of the flag name
+    while (*arg != '\0' && *arg != '=')
+      arg++;
+
+    // get the value if any
+    if (*arg == '=') {
+      // make a copy so we can NUL-terminate flag name
+      int n = arg - *name;
+      if (n >= buffer_size)
+        Fatal(__FILE__, __LINE__, "CHECK(%s) failed", "n < buffer_size");
+      memcpy(buffer, *name, n * sizeof(char));
+      buffer[n] = '\0';
+      *name = buffer;
+      // get the value
+      *value = arg + 1;
+    }
+  }
+}
+
+
+int FlagList::SetFlagsFromCommandLine(int* argc, const char** argv,
+                                      bool remove_flags) {
+  // parse arguments
+  for (int i = 1; i < *argc; /* see below */) {
+    int j = i;  // j > 0
+    const char* arg = argv[i++];
+
+    // split arg into flag components
+    char buffer[1024];
+    const char* name;
+    const char* value;
+    bool is_bool;
+    SplitArgument(arg, buffer, sizeof buffer, &name, &value, &is_bool);
+
+    if (name != NULL) {
+      // lookup the flag
+      Flag* flag = Lookup(name);
+      if (flag == NULL) {
+        fprintf(stderr, "Error: unrecognized flag %s\n", arg);
+        return j;
+      }
+
+      // if we still need a flag value, use the next argument if available
+      if (flag->type() != Flag::BOOL && value == NULL) {
+        if (i < *argc) {
+          value = argv[i++];
+        } else {
+          fprintf(stderr, "Error: missing value for flag %s of type %s\n",
+            arg, Type2String(flag->type()));
+          return j;
+        }
+      }
+
+      // set the flag
+      char empty[] = { '\0' };
+      char* endp = empty;
+      switch (flag->type()) {
+        case Flag::BOOL:
+          *flag->bool_variable() = !is_bool;
+          break;
+        case Flag::INT:
+          *flag->int_variable() = strtol(value, &endp, 10);
+          break;
+        case Flag::FLOAT:
+          *flag->float_variable() = strtod(value, &endp);
+          break;
+        case Flag::STRING:
+          *flag->string_variable() = value;
+          break;
+      }
+
+      // handle errors
+      if ((flag->type() == Flag::BOOL && value != NULL) ||
+          (flag->type() != Flag::BOOL && is_bool) ||
+          *endp != '\0') {
+        fprintf(stderr, "Error: illegal value for flag %s of type %s\n",
+          arg, Type2String(flag->type()));
+        return j;
+      }
+
+      // remove the flag & value from the command
+      if (remove_flags)
+        while (j < i)
+          argv[j++] = NULL;
+    }
+  }
+
+  // shrink the argument list
+  if (remove_flags) {
+    int j = 1;
+    for (int i = 1; i < *argc; i++) {
+      if (argv[i] != NULL)
+        argv[j++] = argv[i];
+    }
+    *argc = j;
+  }
+
+  // parsed all flags successfully
+  return 0;
+}
+
+void FlagList::Register(Flag* flag) {
+  assert(flag != NULL && strlen(flag->name()) > 0);
+  if (Lookup(flag->name()) != NULL)
+    Fatal(flag->file(), 0, "flag %s declared twice", flag->name());
+  flag->next_ = list_;
+  list_ = flag;
+}
+
+#ifdef WIN32
+WindowsCommandLineArguments::WindowsCommandLineArguments() {
+  // start by getting the command line.
+  LPTSTR command_line = ::GetCommandLine();
+   // now, convert it to a list of wide char strings.
+  LPWSTR *wide_argv = ::CommandLineToArgvW(command_line, &argc_);
+  // now allocate an array big enough to hold that many string pointers.
+  argv_ = new char*[argc_];
+
+  // iterate over the returned wide strings;
+  for(int i = 0; i < argc_; ++i) {
+    // for each, create a char buffer big enough to hold it; so, find out
+    // how much space we need.
+    int len8 = WideCharToMultiByte(CP_UTF8, 0, wide_argv[i],
+                                   wcslen(wide_argv[i]), NULL, 0,
+                                   NULL, NULL);
+    // then allocate the buffer...
+    char *buffer = new char[1 + len8]; // +1 for trailing \0
+    // and do the conversion.
+    WideCharToMultiByte(CP_UTF8, 0, wide_argv[i],
+                        wcslen(wide_argv[i]), buffer, len8,
+                        NULL, NULL);
+    // WideCharToMultibyte doesn't give us a trailing \0, so we add it.
+    buffer[len8] = '\0';
+    // make sure the argv array has the right string at this point.
+    argv_[i] = buffer;
+  }
+  LocalFree(wide_argv);
+}
+
+WindowsCommandLineArguments::~WindowsCommandLineArguments() {
+  // need to free each string in the array, and then the array.
+  for(int i = 0; i < argc_; i++) {
+    delete[] argv_[i];
+  }
+
+  delete[] argv_;
+}
+#endif  // WIN32
+
diff --git a/talk/base/flags.h b/talk/base/flags.h
new file mode 100644
index 0000000..a6eee00
--- /dev/null
+++ b/talk/base/flags.h
@@ -0,0 +1,281 @@
+/*
+ * libjingle
+ * Copyright 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.
+ */
+
+// Flags are defined and declared using DEFINE_xxx and DECLARE_xxx macros,
+// where xxx is the flag type. Flags are referred to via FLAG_yyy,
+// where yyy is the flag name. For intialization and iteration of flags,
+// see the FlagList class. For full programmatic access to any
+// flag, see the Flag class.
+//
+// The implementation only relies and basic C++ functionality
+// and needs no special library or STL support.
+
+#ifndef TALK_BASE_FLAGS_H__
+#define TALK_BASE_FLAGS_H__
+
+#include <assert.h>
+
+#include "talk/base/checks.h"
+#include "talk/base/common.h"
+
+// Internal use only.
+union FlagValue {
+  // Note: Because in C++ non-bool values are silently converted into
+  // bool values ('bool b = "false";' results in b == true!), we pass
+  // and int argument to New_BOOL as this appears to be safer - sigh.
+  // In particular, it prevents the (not uncommon!) bug where a bool
+  // flag is defined via: DEFINE_bool(flag, "false", "some comment");.
+  static FlagValue New_BOOL(int b) {
+    FlagValue v;
+    v.b = (b != 0);
+    return v;
+  }
+
+  static FlagValue New_INT(int i) {
+    FlagValue v;
+    v.i = i;
+    return v;
+  }
+
+  static FlagValue New_FLOAT(float f) {
+    FlagValue v;
+    v.f = f;
+    return v;
+  }
+
+  static FlagValue New_STRING(const char* s) {
+    FlagValue v;
+    v.s = s;
+    return v;
+  }
+
+  bool b;
+  int i;
+  double f;
+  const char* s;
+};
+
+
+// Each flag can be accessed programmatically via a Flag object.
+class Flag {
+ public:
+  enum Type { BOOL, INT, FLOAT, STRING };
+
+  // Internal use only.
+  Flag(const char* file, const char* name, const char* comment,
+       Type type, void* variable, FlagValue default_);
+
+  // General flag information
+  const char* file() const  { return file_; }
+  const char* name() const  { return name_; }
+  const char* comment() const  { return comment_; }
+
+  // Flag type
+  Type type() const  { return type_; }
+
+  // Flag variables
+  bool* bool_variable() const {
+    assert(type_ == BOOL);
+    return &variable_->b;
+  }
+  
+  int* int_variable() const {
+    assert(type_ == INT);
+    return &variable_->i;
+  }
+  
+  double* float_variable() const {
+    assert(type_ == FLOAT);
+    return &variable_->f;
+  }
+  
+  const char** string_variable() const {
+    assert(type_ == STRING);
+    return &variable_->s;
+  }
+
+  // Default values
+  bool bool_default() const {
+    assert(type_ == BOOL);
+    return default_.b;
+  }
+  
+  int int_default() const {
+    assert(type_ == INT);
+    return default_.i;
+  }
+  
+  double float_default() const {
+    assert(type_ == FLOAT);
+    return default_.f;
+  }
+  
+  const char* string_default() const {
+    assert(type_ == STRING);
+    return default_.s;
+  }
+
+  // Resets a flag to its default value
+  void SetToDefault();
+
+  // Iteration support
+  Flag* next() const  { return next_; }
+
+  // Prints flag information. The current flag value is only printed
+  // if print_current_value is set.
+  void Print(bool print_current_value);
+
+ private:
+  const char* file_;
+  const char* name_;
+  const char* comment_;
+
+  Type type_;
+  FlagValue* variable_;
+  FlagValue default_;
+
+  Flag* next_;
+
+  friend class FlagList;  // accesses next_
+};
+
+
+// Internal use only.
+#define DEFINE_FLAG(type, c_type, name, default, comment) \
+  /* define and initialize the flag */                    \
+  c_type FLAG_##name = (default);                         \
+  /* register the flag */                                 \
+  static Flag Flag_##name(__FILE__, #name, (comment),   \
+                          Flag::type, &FLAG_##name,       \
+                          FlagValue::New_##type(default))
+
+
+// Internal use only.
+#define DECLARE_FLAG(c_type, name)              \
+  /* declare the external flag */               \
+  extern c_type FLAG_##name
+
+
+// Use the following macros to define a new flag:
+#define DEFINE_bool(name, default, comment) \
+  DEFINE_FLAG(BOOL, bool, name, default, comment)
+#define DEFINE_int(name, default, comment) \
+  DEFINE_FLAG(INT, int, name, default, comment)
+#define DEFINE_float(name, default, comment) \
+  DEFINE_FLAG(FLOAT, double, name, default, comment)
+#define DEFINE_string(name, default, comment) \
+  DEFINE_FLAG(STRING, const char*, name, default, comment)
+
+
+// Use the following macros to declare a flag defined elsewhere:
+#define DECLARE_bool(name)  DECLARE_FLAG(bool, name)
+#define DECLARE_int(name)  DECLARE_FLAG(int, name)
+#define DECLARE_float(name)  DECLARE_FLAG(double, name)
+#define DECLARE_string(name)  DECLARE_FLAG(const char*, name)
+
+
+// The global list of all flags.
+class FlagList {
+ public:
+  FlagList();
+
+  // The NULL-terminated list of all flags. Traverse with Flag::next().
+  static Flag* list()  { return list_; }
+
+  // If file != NULL, prints information for all flags defined in file;
+  // otherwise prints information for all flags in all files. The current
+  // flag value is only printed if print_current_value is set.
+  static void Print(const char* file, bool print_current_value);
+
+  // Lookup a flag by name. Returns the matching flag or NULL.
+  static Flag* Lookup(const char* name);
+
+  // Helper function to parse flags: Takes an argument arg and splits it into
+  // a flag name and flag value (or NULL if they are missing). is_bool is set
+  // if the arg started with "-no" or "--no". The buffer may be used to NUL-
+  // terminate the name, it must be large enough to hold any possible name.
+  static void SplitArgument(const char* arg,
+                            char* buffer, int buffer_size,
+                            const char** name, const char** value,
+                            bool* is_bool);
+
+  // Set the flag values by parsing the command line. If remove_flags
+  // is set, the flags and associated values are removed from (argc,
+  // argv). Returns 0 if no error occurred. Otherwise, returns the
+  // argv index > 0 for the argument where an error occurred. In that
+  // case, (argc, argv) will remain unchanged indepdendent of the
+  // remove_flags value, and no assumptions about flag settings should
+  // be made.
+  //
+  // The following syntax for flags is accepted (both '-' and '--' are ok):
+  //
+  //   --flag        (bool flags only)
+  //   --noflag      (bool flags only)
+  //   --flag=value  (non-bool flags only, no spaces around '=')
+  //   --flag value  (non-bool flags only)
+  static int SetFlagsFromCommandLine(int* argc,
+                                     const char** argv,
+                                     bool remove_flags);
+  static inline int SetFlagsFromCommandLine(int* argc,
+                                            char** argv,
+                                            bool remove_flags) {
+    return SetFlagsFromCommandLine(argc, const_cast<const char**>(argv),
+                                   remove_flags);
+  }
+
+  // Registers a new flag. Called during program initialization. Not
+  // thread-safe.
+  static void Register(Flag* flag);
+
+ private:
+  static Flag* list_;
+};
+
+#ifdef WIN32
+// A helper class to translate Windows command line arguments into UTF8,
+// which then allows us to just pass them to the flags system.
+// This encapsulates all the work of getting the command line and translating
+// it to an array of 8-bit strings; all you have to do is create one of these,
+// and then call argc() and argv().
+class WindowsCommandLineArguments {
+ public:
+  WindowsCommandLineArguments();
+  ~WindowsCommandLineArguments();
+
+  int argc() { return argc_; }
+  char **argv() { return argv_; }
+ private:
+  int argc_;
+  char **argv_;
+
+ private:
+  DISALLOW_EVIL_CONSTRUCTORS(WindowsCommandLineArguments);
+};
+#endif  // WIN32
+
+
+#endif  // SHARED_COMMANDLINEFLAGS_FLAGS_H__
diff --git a/talk/base/helpers.cc b/talk/base/helpers.cc
new file mode 100644
index 0000000..f8d8a9f
--- /dev/null
+++ b/talk/base/helpers.cc
@@ -0,0 +1,257 @@
+/*
+ * 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/base/helpers.h"
+
+#ifdef WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <ntsecapi.h>
+#else
+#ifdef SSL_USE_OPENSSL
+#include <openssl/rand.h>
+#endif
+#endif
+
+#include "talk/base/base64.h"
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/time.h"
+
+namespace talk_base {
+
+// Base class for RNG implementations.
+class RandomGenerator {
+ public:
+  virtual ~RandomGenerator() {}
+  virtual bool Init(const void* seed, size_t len) = 0;
+  virtual bool Generate(void* buf, size_t len) = 0;
+};
+
+// The real random generators, using either CryptoAPI or OpenSSL.
+// We also support the 'old' generator on Mac/Linux until we have time to
+// fully test the OpenSSL one.
+#ifdef WIN32
+class SecureRandomGenerator : public RandomGenerator {
+ public:
+  SecureRandomGenerator() : advapi32_(NULL), rtl_gen_random_(NULL) {}
+  ~SecureRandomGenerator() {
+    FreeLibrary(advapi32_);
+  }
+
+  virtual bool Init(const void* seed, size_t seed_len) {
+    // We don't do any additional seeding on Win32, we just use the CryptoAPI
+    // RNG (which is exposed as a hidden function off of ADVAPI32 so that we
+    // don't need to drag in all of CryptoAPI)
+    if (rtl_gen_random_) {
+      return true;
+    }
+
+    advapi32_ = LoadLibrary(L"advapi32.dll");
+    if (!advapi32_) {
+      return false;
+    }
+
+    rtl_gen_random_ = reinterpret_cast<RtlGenRandomProc>(
+        GetProcAddress(advapi32_, "SystemFunction036"));
+    if (!rtl_gen_random_) {
+      FreeLibrary(advapi32_);
+      return false;
+    }
+
+    return true;
+  }
+  virtual bool Generate(void* buf, size_t len) {
+    if (!rtl_gen_random_ && !Init(NULL, 0)) {
+      return false;
+    }
+    return (rtl_gen_random_(buf, len) != FALSE);
+  }
+
+ private:
+  typedef BOOL (WINAPI *RtlGenRandomProc)(PVOID, ULONG);
+  HINSTANCE advapi32_;
+  RtlGenRandomProc rtl_gen_random_;
+};
+#else
+#ifndef SSL_USE_OPENSSL
+// The old RNG.
+class SecureRandomGenerator : public RandomGenerator {
+ public:
+  SecureRandomGenerator() : seed_(1) {
+  }
+  ~SecureRandomGenerator() {
+  }
+  virtual bool Init(const void* seed, size_t len) {
+    uint32 hash = 0;
+    for (size_t i = 0; i < len; ++i) {
+      hash = ((hash << 2) + hash) + static_cast<const char*>(seed)[i];
+    }
+
+    seed_ = Time() ^ hash;
+    return true;
+  }
+  virtual bool Generate(void* buf, size_t len) {
+    for (size_t i = 0; i < len; ++i) {
+      static_cast<uint8*>(buf)[i] = static_cast<uint8>(GetRandom());
+    }
+    return true;
+  }
+
+ private:
+  int GetRandom() {
+    return ((seed_ = seed_ * 214013L + 2531011L) >> 16) & 0x7fff;
+  }
+  int seed_;
+};
+#else
+// The OpenSSL RNG. Need to make sure it doesn't run out of entropy.
+class SecureRandomGenerator : public RandomGenerator {
+ public:
+  SecureRandomGenerator() : inited_(false) {
+  }
+  ~SecureRandomGenerator() {
+  }
+  virtual bool Init(const void* seed, size_t len) {
+    // By default, seed from the system state.
+    if (!inited_) {
+      if (RAND_poll() <= 0) {
+        return false;
+      }
+      inited_ = true;
+    }
+    // Allow app data to be mixed in, if provided.
+    if (seed) {
+      RAND_seed(seed, len);
+    }
+    return true;
+  }
+  virtual bool Generate(void* buf, size_t len) {
+    if (!inited_ && !Init(NULL, 0)) {
+      return false;
+    }
+    return (RAND_bytes(reinterpret_cast<unsigned char*>(buf), len) > 0);
+  }
+
+ private:
+  bool inited_;
+};
+#endif  // SSL_USE_OPENSSL
+#endif  // WIN32
+
+// A test random generator, for predictable output.
+class TestRandomGenerator : public RandomGenerator {
+ public:
+  TestRandomGenerator() : seed_(7) {
+  }
+  ~TestRandomGenerator() {
+  }
+  virtual bool Init(const void* seed, size_t len) {
+    return true;
+  }
+  virtual bool Generate(void* buf, size_t len) {
+    for (size_t i = 0; i < len; ++i) {
+      static_cast<uint8*>(buf)[i] = static_cast<uint8>(GetRandom());
+    }
+    return true;
+  }
+
+ private:
+  int GetRandom() {
+    return ((seed_ = seed_ * 214013L + 2531011L) >> 16) & 0x7fff;
+  }
+  int seed_;
+};
+
+// TODO: Use Base64::Base64Table instead.
+static const char BASE64[64] = {
+  'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+  'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+  'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+  'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+  '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
+};
+
+static scoped_ptr<RandomGenerator> g_rng(new SecureRandomGenerator());
+
+void SetRandomTestMode(bool test) {
+  if (!test) {
+    g_rng.reset(new SecureRandomGenerator());
+  } else {
+    g_rng.reset(new TestRandomGenerator());
+  }
+}
+
+bool InitRandom(int seed) {
+  return InitRandom(reinterpret_cast<const char*>(&seed), sizeof(seed));
+}
+
+bool InitRandom(const char* seed, size_t len) {
+  if (!g_rng->Init(seed, len)) {
+    LOG(LS_ERROR) << "Failed to init random generator!";
+    return false;
+  }
+  return true;
+}
+
+std::string CreateRandomString(size_t len) {
+  std::string str;
+  CreateRandomString(len, &str);
+  return str;
+}
+
+bool CreateRandomString(size_t len, std::string* str) {
+  str->clear();
+  scoped_array<uint8> bytes(new uint8[len]);
+  if (!g_rng->Generate(bytes.get(), len)) {
+    LOG(LS_ERROR) << "Failed to generate random string!";
+    return false;
+  }
+  str->reserve(len);
+  for (size_t i = 0; i < len; ++i) {
+    str->push_back(BASE64[bytes[i] & 63]);
+  }
+  return true;
+}
+
+uint32 CreateRandomId() {
+  uint32 id;
+  if (!g_rng->Generate(&id, sizeof(id))) {
+    LOG(LS_ERROR) << "Failed to generate random id!";
+  }
+  return id;
+}
+
+uint32 CreateRandomNonZeroId() {
+  uint32 id;
+  do {
+    id = CreateRandomId();
+  } while (id == 0);
+  return id;
+}
+
+}  // namespace talk_base
diff --git a/talk/base/helpers.h b/talk/base/helpers.h
new file mode 100644
index 0000000..83a3c7f
--- /dev/null
+++ b/talk/base/helpers.h
@@ -0,0 +1,61 @@
+/*
+ * 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_BASE_HELPERS_H_
+#define TALK_BASE_HELPERS_H_
+
+#include <string>
+#include "talk/base/basictypes.h"
+
+namespace talk_base {
+
+// For testing, we can return predictable data.
+void SetRandomTestMode(bool test);
+
+// Initializes the RNG, and seeds it with the specified entropy.
+bool InitRandom(int seed);
+bool InitRandom(const char* seed, size_t len);
+
+// Generates a (cryptographically) random string of the given length.
+// We generate base64 values so that they will be printable.
+// WARNING: could silently fail. Use the version below instead.
+std::string CreateRandomString(size_t length);
+
+// Generates a (cryptographically) random string of the given length.
+// We generate base64 values so that they will be printable.
+// Return false if the random number generator failed.
+bool CreateRandomString(size_t length, std::string* str);
+
+// Generates a random id.
+uint32 CreateRandomId();
+
+// Generates a random id > 0.
+uint32 CreateRandomNonZeroId();
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_HELPERS_H_
diff --git a/talk/base/host.cc b/talk/base/host.cc
new file mode 100644
index 0000000..7decc49
--- /dev/null
+++ b/talk/base/host.cc
@@ -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.
+ */
+
+#include "talk/base/host.h"
+
+#ifdef POSIX
+#include <sys/utsname.h>
+#endif  // POSIX
+
+#include <string>
+
+namespace talk_base {
+
+std::string GetHostName() {
+  // TODO: fix or get rid of this
+#if 0
+  struct utsname nm;
+  if (uname(&nm) < 0)
+    FatalError("uname", LAST_SYSTEM_ERROR);
+  return std::string(nm.nodename);
+#endif
+  return "cricket";
+}
+
+}  // namespace talk_base
diff --git a/talk/base/host.h b/talk/base/host.h
new file mode 100644
index 0000000..8528240
--- /dev/null
+++ b/talk/base/host.h
@@ -0,0 +1,40 @@
+/*
+ * 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_BASE_HOST_H_
+#define TALK_BASE_HOST_H_
+
+#include <string>
+
+namespace talk_base {
+
+// Returns the name of the local host.
+std::string GetHostName();
+
+} // namespace talk_base
+
+#endif // TALK_BASE_HOST_H_
diff --git a/talk/base/httpbase.cc b/talk/base/httpbase.cc
new file mode 100644
index 0000000..f5dc60e
--- /dev/null
+++ b/talk/base/httpbase.cc
@@ -0,0 +1,894 @@
+/*
+ * 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.
+ */
+
+// Copyright 2005 Google Inc.  All Rights Reserved.
+//
+
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#else  // !WIN32
+#define SEC_E_CERT_EXPIRED (-2146893016)
+#endif  // !WIN32
+
+#include "talk/base/common.h"
+#include "talk/base/httpbase.h"
+#include "talk/base/logging.h"
+#include "talk/base/socket.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+//////////////////////////////////////////////////////////////////////
+// Helpers
+//////////////////////////////////////////////////////////////////////
+
+bool MatchHeader(const char* str, size_t len, HttpHeader header) {
+  const char* const header_str = ToString(header);
+  const size_t header_len = strlen(header_str);
+  return (len == header_len) && (_strnicmp(str, header_str, header_len) == 0);
+}
+
+enum {
+  MSG_READ
+};
+
+//////////////////////////////////////////////////////////////////////
+// HttpParser
+//////////////////////////////////////////////////////////////////////
+
+HttpParser::HttpParser() {
+  reset();
+}
+
+HttpParser::~HttpParser() {
+}
+
+void
+HttpParser::reset() {
+  state_ = ST_LEADER;
+  chunked_ = false;
+  data_size_ = SIZE_UNKNOWN;
+}
+
+HttpParser::ProcessResult
+HttpParser::Process(const char* buffer, size_t len, size_t* processed,
+                    HttpError* error) {
+  *processed = 0;
+  *error = HE_NONE;
+
+  if (state_ >= ST_COMPLETE) {
+    ASSERT(false);
+    return PR_COMPLETE;
+  }
+
+  while (true) {
+    if (state_ < ST_DATA) {
+      size_t pos = *processed;
+      while ((pos < len) && (buffer[pos] != '\n')) {
+        pos += 1;
+      }
+      if (pos >= len) {
+        break;  // don't have a full header
+      }
+      const char* line = buffer + *processed;
+      size_t len = (pos - *processed);
+      *processed = pos + 1;
+      while ((len > 0) && isspace(static_cast<unsigned char>(line[len-1]))) {
+        len -= 1;
+      }
+      ProcessResult result = ProcessLine(line, len, error);
+      LOG(LS_VERBOSE) << "Processed line, result=" << result;
+
+      if (PR_CONTINUE != result) {
+        return result;
+      }
+    } else if (data_size_ == 0) {
+      if (chunked_) {
+        state_ = ST_CHUNKTERM;
+      } else {
+        return PR_COMPLETE;
+      }
+    } else {
+      size_t available = len - *processed;
+      if (available <= 0) {
+        break; // no more data
+      }
+      if ((data_size_ != SIZE_UNKNOWN) && (available > data_size_)) {
+        available = data_size_;
+      }
+      size_t read = 0;
+      ProcessResult result = ProcessData(buffer + *processed, available, read,
+                                         error);
+      LOG(LS_VERBOSE) << "Processed data, result: " << result << " read: "
+                      << read << " err: " << error;
+
+      if (PR_CONTINUE != result) {
+        return result;
+      }
+      *processed += read;
+      if (data_size_ != SIZE_UNKNOWN) {
+        data_size_ -= read;
+      }
+    }
+  }
+
+  return PR_CONTINUE;
+}
+
+HttpParser::ProcessResult
+HttpParser::ProcessLine(const char* line, size_t len, HttpError* error) {
+  LOG_F(LS_VERBOSE) << " state: " << state_ << " line: "
+                    << std::string(line, len) << " len: " << len << " err: "
+                    << error;
+
+  switch (state_) {
+  case ST_LEADER:
+    state_ = ST_HEADERS;
+    return ProcessLeader(line, len, error);
+
+  case ST_HEADERS:
+    if (len > 0) {
+      const char* value = strchrn(line, len, ':');
+      if (!value) {
+        *error = HE_PROTOCOL;
+        return PR_COMPLETE;
+      }
+      size_t nlen = (value - line);
+      const char* eol = line + len;
+      do {
+        value += 1;
+      } while ((value < eol) && isspace(static_cast<unsigned char>(*value)));
+      size_t vlen = eol - value;
+      if (MatchHeader(line, nlen, HH_CONTENT_LENGTH)) {
+	unsigned int temp_size;
+        if (sscanf(value, "%u", &temp_size) != 1) {
+          *error = HE_PROTOCOL;
+          return PR_COMPLETE;
+        }
+	data_size_ = static_cast<size_t>(temp_size);
+      } else if (MatchHeader(line, nlen, HH_TRANSFER_ENCODING)) {
+        if ((vlen == 7) && (_strnicmp(value, "chunked", 7) == 0)) {
+          chunked_ = true;
+        } else if ((vlen == 8) && (_strnicmp(value, "identity", 8) == 0)) {
+          chunked_ = false;
+        } else {
+          *error = HE_PROTOCOL;
+          return PR_COMPLETE;
+        }
+      }
+      return ProcessHeader(line, nlen, value, vlen, error);
+    } else {
+      state_ = chunked_ ? ST_CHUNKSIZE : ST_DATA;
+      return ProcessHeaderComplete(chunked_, data_size_, error);
+    }
+    break;
+
+  case ST_CHUNKSIZE:
+    if (len > 0) {
+      char* ptr = NULL;
+      data_size_ = strtoul(line, &ptr, 16);
+      if (ptr != line + len) {
+        *error = HE_PROTOCOL;
+        return PR_COMPLETE;
+      }
+      state_ = (data_size_ == 0) ? ST_TRAILERS : ST_DATA;
+    } else {
+      *error = HE_PROTOCOL;
+      return PR_COMPLETE;
+    }
+    break;
+
+  case ST_CHUNKTERM:
+    if (len > 0) {
+      *error = HE_PROTOCOL;
+      return PR_COMPLETE;
+    } else {
+      state_ = chunked_ ? ST_CHUNKSIZE : ST_DATA;
+    }
+    break;
+
+  case ST_TRAILERS:
+    if (len == 0) {
+      return PR_COMPLETE;
+    }
+    // *error = onHttpRecvTrailer();
+    break;
+
+  default:
+    ASSERT(false);
+    break;
+  }
+
+  return PR_CONTINUE;
+}
+
+bool
+HttpParser::is_valid_end_of_input() const {
+  return (state_ == ST_DATA) && (data_size_ == SIZE_UNKNOWN);
+}
+
+void
+HttpParser::complete(HttpError error) {
+  if (state_ < ST_COMPLETE) {
+    state_ = ST_COMPLETE;
+    OnComplete(error);
+  }
+}
+
+//////////////////////////////////////////////////////////////////////
+// HttpBase::DocumentStream
+//////////////////////////////////////////////////////////////////////
+
+class BlockingMemoryStream : public ExternalMemoryStream {
+public:
+  BlockingMemoryStream(char* buffer, size_t size)
+  : ExternalMemoryStream(buffer, size) { }
+
+  virtual StreamResult DoReserve(size_t size, int* error) {
+    return (buffer_length_ >= size) ? SR_SUCCESS : SR_BLOCK;
+  }
+};
+
+class HttpBase::DocumentStream : public StreamInterface {
+public:
+  DocumentStream(HttpBase* base) : base_(base), error_(HE_DEFAULT) { }
+
+  virtual StreamState GetState() const {
+    if (NULL == base_)
+      return SS_CLOSED;
+    if (HM_RECV == base_->mode_)
+      return SS_OPEN;
+    return SS_OPENING;
+  }
+
+  virtual StreamResult Read(void* buffer, size_t buffer_len,
+                            size_t* read, int* error) {
+    if (!base_) {
+      if (error) *error = error_;
+      return (HE_NONE == error_) ? SR_EOS : SR_ERROR;
+    }
+
+    if (HM_RECV != base_->mode_) {
+      return SR_BLOCK;
+    }
+
+    // DoReceiveLoop writes http document data to the StreamInterface* document
+    // member of HttpData.  In this case, we want this data to be written
+    // directly to our buffer.  To accomplish this, we wrap our buffer with a
+    // StreamInterface, and replace the existing document with our wrapper.
+    // When the method returns, we restore the old document.  Ideally, we would
+    // pass our StreamInterface* to DoReceiveLoop, but due to the callbacks
+    // of HttpParser, we would still need to store the pointer temporarily.
+    scoped_ptr<StreamInterface>
+        stream(new BlockingMemoryStream(reinterpret_cast<char*>(buffer),
+                                        buffer_len));
+
+    // Replace the existing document with our wrapped buffer.
+    base_->data_->document.swap(stream);
+
+    // Pump the I/O loop.  DoReceiveLoop is guaranteed not to attempt to
+    // complete the I/O process, which means that our wrapper is not in danger
+    // of being deleted.  To ensure this, DoReceiveLoop returns true when it
+    // wants complete to be called.  We make sure to uninstall our wrapper
+    // before calling complete().
+    HttpError http_error;
+    bool complete = base_->DoReceiveLoop(&http_error);
+
+    // Reinstall the original output document.
+    base_->data_->document.swap(stream);
+
+    // If we reach the end of the receive stream, we disconnect our stream
+    // adapter from the HttpBase, and further calls to read will either return
+    // EOS or ERROR, appropriately.  Finally, we call complete().
+    StreamResult result = SR_BLOCK;
+    if (complete) {
+      HttpBase* base = Disconnect(http_error);
+      if (error) *error = error_;
+      result = (HE_NONE == error_) ? SR_EOS : SR_ERROR;
+      base->complete(http_error);
+    }
+
+    // Even if we are complete, if some data was read we must return SUCCESS.
+    // Future Reads will return EOS or ERROR based on the error_ variable.
+    size_t position;
+    stream->GetPosition(&position);
+    if (position > 0) {
+      if (read) *read = position;
+      result = SR_SUCCESS;
+    }
+    return result;
+  }
+
+  virtual StreamResult Write(const void* data, size_t data_len,
+                             size_t* written, int* error) {
+    if (error) *error = -1;
+    return SR_ERROR;
+  }
+
+  virtual void Close() {
+    if (base_) {
+      HttpBase* base = Disconnect(HE_NONE);
+      if (HM_RECV == base->mode_ && base->http_stream_) {
+        // Read I/O could have been stalled on the user of this DocumentStream,
+        // so restart the I/O process now that we've removed ourselves.
+        base->http_stream_->PostEvent(SE_READ, 0);
+      }
+    }
+  }
+
+  virtual bool GetAvailable(size_t* size) const {
+    if (!base_ || HM_RECV != base_->mode_)
+      return false;
+    size_t data_size = base_->GetDataRemaining();
+    if (SIZE_UNKNOWN == data_size)
+      return false;
+    if (size)
+      *size = data_size;
+    return true;
+  }
+
+  HttpBase* Disconnect(HttpError error) {
+    ASSERT(NULL != base_);
+    ASSERT(NULL != base_->doc_stream_);
+    HttpBase* base = base_;
+    base_->doc_stream_ = NULL;
+    base_ = NULL;
+    error_ = error;
+    return base;
+  }
+
+private:
+  HttpBase* base_;
+  HttpError error_;
+};
+
+//////////////////////////////////////////////////////////////////////
+// HttpBase
+//////////////////////////////////////////////////////////////////////
+
+HttpBase::HttpBase() : mode_(HM_NONE), data_(NULL), notify_(NULL),
+                       http_stream_(NULL), doc_stream_(NULL) {
+}
+
+HttpBase::~HttpBase() {
+  ASSERT(HM_NONE == mode_);
+}
+
+bool
+HttpBase::isConnected() const {
+  return (http_stream_ != NULL) && (http_stream_->GetState() == SS_OPEN);
+}
+
+bool
+HttpBase::attach(StreamInterface* stream) {
+  if ((mode_ != HM_NONE) || (http_stream_ != NULL) || (stream == NULL)) {
+    ASSERT(false);
+    return false;
+  }
+  http_stream_ = stream;
+  http_stream_->SignalEvent.connect(this, &HttpBase::OnHttpStreamEvent);
+  mode_ = (http_stream_->GetState() == SS_OPENING) ? HM_CONNECT : HM_NONE;
+  return true;
+}
+
+StreamInterface*
+HttpBase::detach() {
+  ASSERT(HM_NONE == mode_);
+  if (mode_ != HM_NONE) {
+    return NULL;
+  }
+  StreamInterface* stream = http_stream_;
+  http_stream_ = NULL;
+  if (stream) {
+    stream->SignalEvent.disconnect(this);
+  }
+  return stream;
+}
+
+void
+HttpBase::send(HttpData* data) {
+  ASSERT(HM_NONE == mode_);
+  if (mode_ != HM_NONE) {
+    return;
+  } else if (!isConnected()) {
+    OnHttpStreamEvent(http_stream_, SE_CLOSE, HE_DISCONNECTED);
+    return;
+  }
+
+  mode_ = HM_SEND;
+  data_ = data;
+  len_ = 0;
+  ignore_data_ = chunk_data_ = false;
+
+  if (data_->document.get()) {
+    data_->document->SignalEvent.connect(this, &HttpBase::OnDocumentEvent);
+  }
+
+  std::string encoding;
+  if (data_->hasHeader(HH_TRANSFER_ENCODING, &encoding)
+      && (encoding == "chunked")) {
+    chunk_data_ = true;
+  }
+
+  len_ = data_->formatLeader(buffer_, sizeof(buffer_));
+  len_ += strcpyn(buffer_ + len_, sizeof(buffer_) - len_, "\r\n");
+
+  header_ = data_->begin();
+  if (header_ == data_->end()) {
+    // We must call this at least once, in the case where there are no headers.
+    queue_headers();
+  }
+
+  flush_data();
+}
+
+void
+HttpBase::recv(HttpData* data) {
+  ASSERT(HM_NONE == mode_);
+  if (mode_ != HM_NONE) {
+    return;
+  } else if (!isConnected()) {
+    OnHttpStreamEvent(http_stream_, SE_CLOSE, HE_DISCONNECTED);
+    return;
+  }
+
+  mode_ = HM_RECV;
+  data_ = data;
+  len_ = 0;
+  ignore_data_ = chunk_data_ = false;
+
+  reset();
+  if (doc_stream_) {
+    doc_stream_->SignalEvent(doc_stream_, SE_OPEN | SE_READ, 0);
+  } else {
+    read_and_process_data();
+  }
+}
+
+void
+HttpBase::abort(HttpError err) {
+  if (mode_ != HM_NONE) {
+    if (http_stream_ != NULL) {
+      http_stream_->Close();
+    }
+    do_complete(err);
+  }
+}
+
+StreamInterface* HttpBase::GetDocumentStream() {
+  if (doc_stream_)
+    return NULL;
+  doc_stream_ = new DocumentStream(this);
+  return doc_stream_;
+}
+
+HttpError HttpBase::HandleStreamClose(int error) {
+  if (http_stream_ != NULL) {
+    http_stream_->Close();
+  }
+  if (error == 0) {
+    if ((mode_ == HM_RECV) && is_valid_end_of_input()) {
+      return HE_NONE;
+    } else {
+      return HE_DISCONNECTED;
+    }
+  } else if (error == SOCKET_EACCES) {
+    return HE_AUTH;
+  } else if (error == SEC_E_CERT_EXPIRED) {
+    return HE_CERTIFICATE_EXPIRED;
+  }
+  LOG_F(LS_ERROR) << "(" << error << ")";
+  return (HM_CONNECT == mode_) ? HE_CONNECT_FAILED : HE_SOCKET_ERROR;
+}
+
+bool HttpBase::DoReceiveLoop(HttpError* error) {
+  ASSERT(HM_RECV == mode_);
+  ASSERT(NULL != error);
+
+  // Do to the latency between receiving read notifications from
+  // pseudotcpchannel, we rely on repeated calls to read in order to acheive
+  // ideal throughput.  The number of reads is limited to prevent starving
+  // the caller.
+
+  size_t loop_count = 0;
+  const size_t kMaxReadCount = 20;
+  bool process_requires_more_data = false;
+  do {
+    // The most frequent use of this function is response to new data available
+    // on http_stream_.  Therefore, we optimize by attempting to read from the
+    // network first (as opposed to processing existing data first).
+
+    if (len_ < sizeof(buffer_)) {
+      // Attempt to buffer more data.
+      size_t read;
+      int read_error;
+      StreamResult read_result = http_stream_->Read(buffer_ + len_,
+                                                    sizeof(buffer_) - len_,
+                                                    &read, &read_error);
+      switch (read_result) {
+      case SR_SUCCESS:
+        ASSERT(len_ + read <= sizeof(buffer_));
+        len_ += read;
+        break;
+      case SR_BLOCK:
+        if (process_requires_more_data) {
+          // We're can't make progress until more data is available.
+          return false;
+        }
+        // Attempt to process the data already in our buffer.
+        break;
+      case SR_EOS:
+        // Clean close, with no error.  Fall through to HandleStreamClose.
+        read_error = 0;
+      case SR_ERROR:
+        *error = HandleStreamClose(read_error);
+        return true;
+      }
+    } else if (process_requires_more_data) {
+      // We have too much unprocessed data in our buffer.  This should only
+      // occur when a single HTTP header is longer than the buffer size (32K).
+      // Anything longer than that is almost certainly an error.
+      *error = HE_OVERFLOW;
+      return true;
+    }
+
+    // Process data in our buffer.  Process is not guaranteed to process all
+    // the buffered data.  In particular, it will wait until a complete
+    // protocol element (such as http header, or chunk size) is available,
+    // before processing it in its entirety.  Also, it is valid and sometimes
+    // necessary to call Process with an empty buffer, since the state machine
+    // may have interrupted state transitions to complete.
+    size_t processed;
+    ProcessResult process_result = Process(buffer_, len_, &processed,
+                                            error);
+    ASSERT(processed <= len_);
+    len_ -= processed;
+    memmove(buffer_, buffer_ + processed, len_);
+    switch (process_result) {
+    case PR_CONTINUE:
+      // We need more data to make progress.
+      process_requires_more_data = true;
+      break;
+    case PR_BLOCK:
+      // We're stalled on writing the processed data.
+      return false;
+    case PR_COMPLETE:
+      // *error already contains the correct code.
+      return true;
+    }
+  } while (++loop_count <= kMaxReadCount);
+
+  LOG_F(LS_WARNING) << "danger of starvation";
+  return false;
+}
+
+void
+HttpBase::read_and_process_data() {
+  HttpError error;
+  if (DoReceiveLoop(&error)) {
+    complete(error);
+  }
+}
+
+void
+HttpBase::flush_data() {
+  ASSERT(HM_SEND == mode_);
+
+  // When send_required is true, no more buffering can occur without a network
+  // write.
+  bool send_required = (len_ >= sizeof(buffer_));
+
+  while (true) {
+    ASSERT(len_ <= sizeof(buffer_));
+
+    // HTTP is inherently sensitive to round trip latency, since a frequent use
+    // case is for small requests and responses to be sent back and forth, and
+    // the lack of pipelining forces a single request to take a minimum of the
+    // round trip time.  As a result, it is to our benefit to pack as much data
+    // into each packet as possible.  Thus, we defer network writes until we've
+    // buffered as much data as possible.
+
+    if (!send_required && (header_ != data_->end())) {
+      // First, attempt to queue more header data.
+      send_required = queue_headers();
+    }
+
+    if (!send_required && (NULL != data_->document.get())) {
+      // Next, attempt to queue document data.
+
+      const size_t kChunkDigits = 8;
+      size_t offset, reserve;
+      if (chunk_data_) {
+        // Reserve characters at the start for X-byte hex value and \r\n
+        offset = len_ + kChunkDigits + 2;
+        // ... and 2 characters at the end for \r\n
+        reserve = offset + 2;
+      } else {
+        offset = len_;
+        reserve = offset;
+      }
+
+      if (reserve >= sizeof(buffer_)) {
+        send_required = true;
+      } else {
+        size_t read;
+        int error;
+        StreamResult result = data_->document->Read(buffer_ + offset,
+                                                    sizeof(buffer_) - reserve,
+                                                    &read, &error);
+        if (result == SR_SUCCESS) {
+          ASSERT(reserve + read <= sizeof(buffer_));
+          if (chunk_data_) {
+            // Prepend the chunk length in hex.
+            // Note: sprintfn appends a null terminator, which is why we can't
+            // combine it with the line terminator.
+            sprintfn(buffer_ + len_, kChunkDigits + 1, "%.*x",
+                     kChunkDigits, read);
+            // Add line terminator to the chunk length.
+            memcpy(buffer_ + len_ + kChunkDigits, "\r\n", 2);
+            // Add line terminator to the end of the chunk.
+            memcpy(buffer_ + offset + read, "\r\n", 2);
+          }
+          len_ = reserve + read;
+        } else if (result == SR_BLOCK) {
+          // Nothing to do but flush data to the network.
+          send_required = true;
+        } else if (result == SR_EOS) {
+          if (chunk_data_) {
+            // Append the empty chunk and empty trailers, then turn off
+            // chunking.
+            ASSERT(len_ + 5 <= sizeof(buffer_));
+            memcpy(buffer_ + len_, "0\r\n\r\n", 5);
+            len_ += 5;
+            chunk_data_ = false;
+          } else if (0 == len_) {
+            // No more data to read, and no more data to write.
+            do_complete();
+            return;
+          }
+          // Although we are done reading data, there is still data which needs
+          // to be flushed to the network.
+          send_required = true;
+        } else {
+          LOG_F(LS_ERROR) << "Read error: " << error;
+          do_complete(HE_STREAM);
+          return;
+        }
+      }
+    }
+
+    if (0 == len_) {
+      // No data currently available to send.
+      if (NULL == data_->document.get()) {
+        // If there is no source document, that means we're done.
+        do_complete();
+      }
+      return;
+    }
+
+    size_t written;
+    int error;
+    StreamResult result = http_stream_->Write(buffer_, len_, &written, &error);
+    if (result == SR_SUCCESS) {
+      ASSERT(written <= len_);
+      len_ -= written;
+      memmove(buffer_, buffer_ + written, len_);
+      send_required = false;
+    } else if (result == SR_BLOCK) {
+      if (send_required) {
+        // Nothing more we can do until network is writeable.
+        return;
+      }
+    } else {
+      ASSERT(result == SR_ERROR);
+      LOG_F(LS_ERROR) << "error";
+      OnHttpStreamEvent(http_stream_, SE_CLOSE, error);
+      return;
+    }
+  }
+
+  ASSERT(false);
+}
+
+bool
+HttpBase::queue_headers() {
+  ASSERT(HM_SEND == mode_);
+  while (header_ != data_->end()) {
+    size_t len = sprintfn(buffer_ + len_, sizeof(buffer_) - len_,
+                          "%.*s: %.*s\r\n",
+                          header_->first.size(), header_->first.data(),
+                          header_->second.size(), header_->second.data());
+    if (len_ + len < sizeof(buffer_) - 3) {
+      len_ += len;
+      ++header_;
+    } else if (len_ == 0) {
+      LOG(WARNING) << "discarding header that is too long: " << header_->first;
+      ++header_;
+    } else {
+      // Not enough room for the next header, write to network first.
+      return true;
+    }
+  }
+  // End of headers
+  len_ += strcpyn(buffer_ + len_, sizeof(buffer_) - len_, "\r\n");
+  return false;
+}
+
+void
+HttpBase::do_complete(HttpError err) {
+  ASSERT(mode_ != HM_NONE);
+  HttpMode mode = mode_;
+  mode_ = HM_NONE;
+  if (data_ && data_->document.get()) {
+    data_->document->SignalEvent.disconnect(this);
+  }
+  data_ = NULL;
+  if ((HM_RECV == mode) && doc_stream_) {
+    ASSERT(HE_NONE != err);  // We should have Disconnected doc_stream_ already.
+    DocumentStream* ds = doc_stream_;
+    ds->Disconnect(err);
+    ds->SignalEvent(ds, SE_CLOSE, err);
+  }
+  if (notify_) {
+    notify_->onHttpComplete(mode, err);
+  }
+}
+
+//
+// Stream Signals
+//
+
+void
+HttpBase::OnHttpStreamEvent(StreamInterface* stream, int events, int error) {
+  ASSERT(stream == http_stream_);
+  if ((events & SE_OPEN) && (mode_ == HM_CONNECT)) {
+    do_complete();
+    return;
+  }
+
+  if ((events & SE_WRITE) && (mode_ == HM_SEND)) {
+    flush_data();
+    return;
+  }
+
+  if ((events & SE_READ) && (mode_ == HM_RECV)) {
+    if (doc_stream_) {
+      doc_stream_->SignalEvent(doc_stream_, SE_READ, 0);
+    } else {
+      read_and_process_data();
+    }
+    return;
+  }
+
+  if ((events & SE_CLOSE) == 0)
+    return;
+
+  HttpError http_error = HandleStreamClose(error);
+  if (mode_ == HM_RECV) {
+    complete(http_error);
+  } else if (mode_ != HM_NONE) {
+    do_complete(http_error);
+  } else if (notify_) {
+    notify_->onHttpClosed(http_error);
+  }
+}
+
+void
+HttpBase::OnDocumentEvent(StreamInterface* stream, int events, int error) {
+  ASSERT(stream == data_->document.get());
+  if ((events & SE_WRITE) && (mode_ == HM_RECV)) {
+    read_and_process_data();
+    return;
+  }
+
+  if ((events & SE_READ) && (mode_ == HM_SEND)) {
+    flush_data();
+    return;
+  }
+
+  if (events & SE_CLOSE) {
+    LOG_F(LS_ERROR) << "Read error: " << error;
+    do_complete(HE_STREAM);
+    return;
+  }
+}
+
+//
+// HttpParser Implementation
+//
+
+HttpParser::ProcessResult
+HttpBase::ProcessLeader(const char* line, size_t len, HttpError* error) {
+  *error = data_->parseLeader(line, len);
+  return (HE_NONE == *error) ? PR_CONTINUE : PR_COMPLETE;
+}
+
+HttpParser::ProcessResult
+HttpBase::ProcessHeader(const char* name, size_t nlen, const char* value,
+                        size_t vlen, HttpError* error) {
+  std::string sname(name, nlen), svalue(value, vlen);
+  data_->addHeader(sname, svalue);
+  return PR_CONTINUE;
+}
+
+HttpParser::ProcessResult
+HttpBase::ProcessHeaderComplete(bool chunked, size_t& data_size,
+                                HttpError* error) {
+  StreamInterface* old_docstream = doc_stream_;
+  if (notify_) {
+    *error = notify_->onHttpHeaderComplete(chunked, data_size);
+    // The request must not be aborted as a result of this callback.
+    ASSERT(NULL != data_);
+  }
+  if ((HE_NONE == *error) && (NULL != data_->document.get())) {
+    data_->document->SignalEvent.connect(this, &HttpBase::OnDocumentEvent);
+  }
+  if (HE_NONE != *error) {
+    return PR_COMPLETE;
+  }
+  if (old_docstream != doc_stream_) {
+    // Break out of Process loop, since our I/O model just changed.
+    return PR_BLOCK;
+  }
+  return PR_CONTINUE;
+}
+
+HttpParser::ProcessResult
+HttpBase::ProcessData(const char* data, size_t len, size_t& read,
+                      HttpError* error) {
+  LOG_F(LS_VERBOSE) << "data: " << std::string(data, len);
+  if (ignore_data_ || !data_->document.get()) {
+    read = len;
+    return PR_CONTINUE;
+  }
+  int write_error = 0;
+  switch (data_->document->Write(data, len, &read, &write_error)) {
+  case SR_SUCCESS:
+    return PR_CONTINUE;
+  case SR_BLOCK:
+    return PR_BLOCK;
+  case SR_EOS:
+    LOG_F(LS_ERROR) << "Unexpected EOS";
+    *error = HE_STREAM;
+    return PR_COMPLETE;
+  case SR_ERROR:
+  default:
+    LOG_F(LS_ERROR) << "Write error: " << write_error;
+    *error = HE_STREAM;
+    return PR_COMPLETE;
+  }
+}
+
+void
+HttpBase::OnComplete(HttpError err) {
+  LOG_F(LS_VERBOSE);
+  do_complete(err);
+}
+
+} // namespace talk_base
diff --git a/talk/base/httpbase.h b/talk/base/httpbase.h
new file mode 100644
index 0000000..97527eb
--- /dev/null
+++ b/talk/base/httpbase.h
@@ -0,0 +1,201 @@
+/*
+ * 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.
+ */
+
+// Copyright 2005 Google Inc.  All Rights Reserved.
+//
+
+
+#ifndef TALK_BASE_HTTPBASE_H__
+#define TALK_BASE_HTTPBASE_H__
+
+#include "talk/base/httpcommon.h"
+
+namespace talk_base {
+
+class StreamInterface;
+
+///////////////////////////////////////////////////////////////////////////////
+// HttpParser - Parses an HTTP stream provided via Process and end_of_input, and
+// generates events for:
+//  Structural Elements: Leader, Headers, Document Data
+//  Events: End of Headers, End of Document, Errors
+///////////////////////////////////////////////////////////////////////////////
+
+class HttpParser {
+public:
+  enum ProcessResult { PR_CONTINUE, PR_BLOCK, PR_COMPLETE };
+  HttpParser();
+  virtual ~HttpParser();
+  
+  void reset();
+  ProcessResult Process(const char* buffer, size_t len, size_t* processed,
+                        HttpError* error);
+  bool is_valid_end_of_input() const;
+  void complete(HttpError err);
+  
+  size_t GetDataRemaining() const { return data_size_; }
+
+protected:
+  ProcessResult ProcessLine(const char* line, size_t len, HttpError* error);
+
+  // HttpParser Interface
+  virtual ProcessResult ProcessLeader(const char* line, size_t len,
+                                      HttpError* error) = 0;
+  virtual ProcessResult ProcessHeader(const char* name, size_t nlen,
+                                      const char* value, size_t vlen,
+                                      HttpError* error) = 0;
+  virtual ProcessResult ProcessHeaderComplete(bool chunked, size_t& data_size,
+                                              HttpError* error) = 0;
+  virtual ProcessResult ProcessData(const char* data, size_t len, size_t& read,
+                                    HttpError* error) = 0;
+  virtual void OnComplete(HttpError err) = 0;
+  
+private:
+  enum State {
+    ST_LEADER, ST_HEADERS,
+    ST_CHUNKSIZE, ST_CHUNKTERM, ST_TRAILERS,
+    ST_DATA, ST_COMPLETE
+  } state_;
+  bool chunked_;
+  size_t data_size_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// IHttpNotify
+///////////////////////////////////////////////////////////////////////////////
+
+enum HttpMode { HM_NONE, HM_CONNECT, HM_RECV, HM_SEND };
+
+class IHttpNotify {
+public:
+  virtual ~IHttpNotify() {}
+  virtual HttpError onHttpHeaderComplete(bool chunked, size_t& data_size) = 0;
+  virtual void onHttpComplete(HttpMode mode, HttpError err) = 0;
+  virtual void onHttpClosed(HttpError err) = 0;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// HttpBase - Provides a state machine for implementing HTTP-based components.
+// Attach HttpBase to a StreamInterface which represents a bidirectional HTTP
+// stream, and then call send() or recv() to initiate sending or receiving one
+// side of an HTTP transaction.  By default, HttpBase operates as an I/O pump,
+// moving data from the HTTP stream to the HttpData object and vice versa.
+// However, it can also operate in stream mode, in which case the user of the
+// stream interface drives I/O via calls to Read().
+///////////////////////////////////////////////////////////////////////////////
+
+class HttpBase
+: private HttpParser,
+  public sigslot::has_slots<>
+{
+public:
+  HttpBase();
+  virtual ~HttpBase();
+
+  void notify(IHttpNotify* notify) { notify_ = notify; }
+  bool attach(StreamInterface* stream);
+  StreamInterface* stream() { return http_stream_; }
+  StreamInterface* detach();
+  bool isConnected() const;
+
+  void send(HttpData* data);
+  void recv(HttpData* data);
+  void abort(HttpError err);
+
+  HttpMode mode() const { return mode_; }
+
+  void set_ignore_data(bool ignore) { ignore_data_ = ignore; }
+  bool ignore_data() const { return ignore_data_; }
+
+  // Obtaining this stream puts HttpBase into stream mode until the stream
+  // is closed.  HttpBase can only expose one open stream interface at a time.
+  // Further calls will return NULL.
+  StreamInterface* GetDocumentStream();
+
+protected:
+  // Do cleanup when the http stream closes (error may be 0 for a clean
+  // shutdown), and return the error code to signal.
+  HttpError HandleStreamClose(int error);
+
+  // DoReceiveLoop acts as a data pump, pulling data from the http stream,
+  // pushing it through the HttpParser, and then populating the HttpData object
+  // based on the callbacks from the parser.  One of the most interesting
+  // callbacks is ProcessData, which provides the actual http document body.
+  // This data is then written to the HttpData::document.  As a result, data
+  // flows from the network to the document, with some incidental protocol
+  // parsing in between.
+  // Ideally, we would pass in the document* to DoReceiveLoop, to more easily
+  // support GetDocumentStream().  However, since the HttpParser is callback
+  // driven, we are forced to store the pointer somewhere until the callback
+  // is triggered.
+  // Returns true if the received document has finished, and
+  // HttpParser::complete should be called.
+  bool DoReceiveLoop(HttpError* err);
+
+  void read_and_process_data();
+  void flush_data();
+  bool queue_headers();
+  void do_complete(HttpError err = HE_NONE);
+
+  void OnHttpStreamEvent(StreamInterface* stream, int events, int error);
+  void OnDocumentEvent(StreamInterface* stream, int events, int error);
+
+  // HttpParser Interface
+  virtual ProcessResult ProcessLeader(const char* line, size_t len,
+                                      HttpError* error);
+  virtual ProcessResult ProcessHeader(const char* name, size_t nlen,
+                                      const char* value, size_t vlen,
+                                      HttpError* error);
+  virtual ProcessResult ProcessHeaderComplete(bool chunked, size_t& data_size,
+                                              HttpError* error);
+  virtual ProcessResult ProcessData(const char* data, size_t len, size_t& read,
+                                    HttpError* error);
+  virtual void OnComplete(HttpError err);
+
+private:
+  class DocumentStream;
+  friend class DocumentStream;
+
+  enum { kBufferSize = 32 * 1024 };
+
+  HttpMode mode_;
+  HttpData* data_;
+  IHttpNotify* notify_;
+  StreamInterface* http_stream_;
+  DocumentStream* doc_stream_;
+  char buffer_[kBufferSize];
+  size_t len_;
+
+  bool ignore_data_, chunk_data_;
+  HttpData::const_iterator header_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
+
+#endif // TALK_BASE_HTTPBASE_H__
diff --git a/talk/base/httpclient.cc b/talk/base/httpclient.cc
new file mode 100644
index 0000000..3b6e97e
--- /dev/null
+++ b/talk/base/httpclient.cc
@@ -0,0 +1,813 @@
+/*
+ * 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 <time.h>
+
+#include "talk/base/httpcommon-inl.h"
+
+#include "talk/base/asyncsocket.h"
+#include "talk/base/common.h"
+#include "talk/base/diskcache.h"
+#include "talk/base/httpclient.h"
+#include "talk/base/logging.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/socketstream.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+//////////////////////////////////////////////////////////////////////
+// Helpers
+//////////////////////////////////////////////////////////////////////
+
+namespace {
+
+const size_t kCacheHeader = 0;
+const size_t kCacheBody = 1;
+
+// Convert decimal string to integer
+bool HttpStringToInt(const std::string& str, unsigned long* val) {
+  ASSERT(NULL != val);
+  char* eos = NULL;
+  *val = strtoul(str.c_str(), &eos, 10);
+  return (*eos == '\0');
+}
+
+bool HttpShouldCache(const HttpTransaction& t) {
+  bool verb_allows_cache = (t.request.verb == HV_GET)
+                           || (t.request.verb == HV_HEAD);
+  bool is_range_response = t.response.hasHeader(HH_CONTENT_RANGE, NULL);
+  bool has_expires = t.response.hasHeader(HH_EXPIRES, NULL);
+  bool request_allows_cache =
+    has_expires || (std::string::npos != t.request.path.find('?'));
+  bool response_allows_cache =
+    has_expires || HttpCodeIsCacheable(t.response.scode);
+
+  bool may_cache = verb_allows_cache
+                   && request_allows_cache
+                   && response_allows_cache
+                   && !is_range_response;
+
+  std::string value;
+  if (t.response.hasHeader(HH_CACHE_CONTROL, &value)) {
+    HttpAttributeList directives;
+    HttpParseAttributes(value.data(), value.size(), directives);
+    // Response Directives Summary:
+    // public - always cacheable
+    // private - do not cache in a shared cache
+    // no-cache - may cache, but must revalidate whether fresh or stale
+    // no-store - sensitive information, do not cache or store in any way
+    // max-age - supplants Expires for staleness
+    // s-maxage - use as max-age for shared caches, ignore otherwise
+    // must-revalidate - may cache, but must revalidate after stale
+    // proxy-revalidate - shared cache must revalidate
+    if (HttpHasAttribute(directives, "no-store", NULL)) {
+      may_cache = false;
+    } else if (HttpHasAttribute(directives, "public", NULL)) {
+      may_cache = true;
+    }
+  }
+  return may_cache;
+}
+
+enum HttpCacheState {
+  HCS_FRESH,  // In cache, may use
+  HCS_STALE,  // In cache, must revalidate
+  HCS_NONE    // Not in cache
+};
+
+HttpCacheState HttpGetCacheState(const HttpTransaction& t) {
+  // Temporaries
+  std::string s_temp;
+  unsigned long i_temp;
+
+  // Current time
+  unsigned long now = time(0);
+
+  HttpAttributeList cache_control;
+  if (t.response.hasHeader(HH_CACHE_CONTROL, &s_temp)) {
+    HttpParseAttributes(s_temp.data(), s_temp.size(), cache_control);
+  }
+
+  // Compute age of cache document
+  unsigned long date;
+  if (!t.response.hasHeader(HH_DATE, &s_temp)
+      || !HttpDateToSeconds(s_temp, &date))
+    return HCS_NONE;
+
+  // TODO: Timestamp when cache request sent and response received?
+  unsigned long request_time = date;
+  unsigned long response_time = date;
+
+  unsigned long apparent_age = 0;
+  if (response_time > date) {
+    apparent_age = response_time - date;
+  }
+
+  unsigned long corrected_received_age = apparent_age;
+  if (t.response.hasHeader(HH_AGE, &s_temp)
+      && HttpStringToInt(s_temp, &i_temp)) {
+    corrected_received_age = stdmax(apparent_age, i_temp);
+  }
+
+  unsigned long response_delay = response_time - request_time;
+  unsigned long corrected_initial_age = corrected_received_age + response_delay;
+  unsigned long resident_time = now - response_time;
+  unsigned long current_age = corrected_initial_age + resident_time;
+
+  // Compute lifetime of document
+  unsigned long lifetime;
+  if (HttpHasAttribute(cache_control, "max-age", &s_temp)) {
+    lifetime = atoi(s_temp.c_str());
+  } else if (t.response.hasHeader(HH_EXPIRES, &s_temp)
+             && HttpDateToSeconds(s_temp, &i_temp)) {
+    lifetime = i_temp - date;
+  } else if (t.response.hasHeader(HH_LAST_MODIFIED, &s_temp)
+             && HttpDateToSeconds(s_temp, &i_temp)) {
+    // TODO: Issue warning 113 if age > 24 hours
+    lifetime = (now - i_temp) / 10;
+  } else {
+    return HCS_STALE;
+  }
+
+  return (lifetime > current_age) ? HCS_FRESH : HCS_STALE;
+}
+
+enum HttpValidatorStrength {
+  HVS_NONE,
+  HVS_WEAK,
+  HVS_STRONG
+};
+
+HttpValidatorStrength
+HttpRequestValidatorLevel(const HttpRequestData& request) {
+  if (HV_GET != request.verb)
+    return HVS_STRONG;
+  return request.hasHeader(HH_RANGE, NULL) ? HVS_STRONG : HVS_WEAK;
+}
+
+HttpValidatorStrength
+HttpResponseValidatorLevel(const HttpResponseData& response) {
+  std::string value;
+  if (response.hasHeader(HH_ETAG, &value)) {
+    bool is_weak = (strnicmp(value.c_str(), "W/", 2) == 0);
+    return is_weak ? HVS_WEAK : HVS_STRONG;
+  }
+  if (response.hasHeader(HH_LAST_MODIFIED, &value)) {
+    unsigned long last_modified, date;
+    if (HttpDateToSeconds(value, &last_modified)
+        && response.hasHeader(HH_DATE, &value)
+        && HttpDateToSeconds(value, &date)
+        && (last_modified + 60 < date)) {
+      return HVS_STRONG;
+    }
+    return HVS_WEAK;
+  }
+  return HVS_NONE;
+}
+
+std::string GetCacheID(const HttpRequestData& request) {
+  std::string id, url;
+  id.append(ToString(request.verb));
+  id.append("_");
+  request.getAbsoluteUri(&url);
+  id.append(url);
+  return id;
+}
+
+}  // anonymous namespace
+
+//////////////////////////////////////////////////////////////////////
+// Public Helpers
+//////////////////////////////////////////////////////////////////////
+
+bool HttpWriteCacheHeaders(const HttpResponseData* response,
+                           StreamInterface* output, size_t* size) {
+  size_t length = 0;
+  // Write all unknown and end-to-end headers to a cache file
+  for (HttpData::const_iterator it = response->begin();
+       it != response->end(); ++it) {
+    HttpHeader header;
+    if (FromString(header, it->first) && !HttpHeaderIsEndToEnd(header))
+      continue;
+    length += it->first.length() + 2 + it->second.length() + 2;
+    if (!output)
+      continue;
+    std::string formatted_header(it->first);
+    formatted_header.append(": ");
+    formatted_header.append(it->second);
+    formatted_header.append("\r\n");
+    StreamResult result = output->WriteAll(formatted_header.data(),
+                                           formatted_header.length(),
+                                           NULL, NULL);
+    if (SR_SUCCESS != result) {
+      return false;
+    }
+  }
+  if (output && (SR_SUCCESS != output->WriteAll("\r\n", 2, NULL, NULL))) {
+    return false;
+  }
+  length += 2;
+  if (size)
+    *size = length;
+  return true;
+}
+
+bool HttpReadCacheHeaders(StreamInterface* input, HttpResponseData* response,
+                          HttpData::HeaderCombine combine) {
+  while (true) {
+    std::string formatted_header;
+    StreamResult result = input->ReadLine(&formatted_header);
+    if ((SR_EOS == result) || (1 == formatted_header.size())) {
+      break;
+    }
+    if (SR_SUCCESS != result) {
+      return false;
+    }
+    size_t end_of_name = formatted_header.find(':');
+    if (std::string::npos == end_of_name) {
+      LOG_F(LS_WARNING) << "Malformed cache header";
+      continue;
+    }
+    size_t start_of_value = end_of_name + 1;
+    size_t end_of_value = formatted_header.length();
+    while ((start_of_value < end_of_value)
+           && isspace(formatted_header[start_of_value]))
+      ++start_of_value;
+    while ((start_of_value < end_of_value)
+           && isspace(formatted_header[end_of_value-1]))
+     --end_of_value;
+    size_t value_length = end_of_value - start_of_value;
+
+    std::string name(formatted_header.substr(0, end_of_name));
+    std::string value(formatted_header.substr(start_of_value, value_length));
+    response->changeHeader(name, value, combine);
+  }
+  return true;
+}
+
+//////////////////////////////////////////////////////////////////////
+// HttpClient
+//////////////////////////////////////////////////////////////////////
+
+const size_t kDefaultRetries = 1;
+const size_t kMaxRedirects = 5;
+
+HttpClient::HttpClient(const std::string& agent, StreamPool* pool,
+                       HttpTransaction* transaction)
+    : agent_(agent), pool_(pool),
+      transaction_(transaction), free_transaction_(false),
+      retries_(kDefaultRetries), attempt_(0), redirects_(0),
+      redirect_action_(REDIRECT_DEFAULT),
+      uri_form_(URI_DEFAULT), cache_(NULL), cache_state_(CS_READY) {
+  base_.notify(this);
+  if (NULL == transaction_) {
+    free_transaction_ = true;
+    transaction_ = new HttpTransaction;
+  }
+}
+
+HttpClient::~HttpClient() {
+  base_.notify(NULL);
+  base_.abort(HE_SHUTDOWN);
+  release();
+  if (free_transaction_)
+    delete transaction_;
+}
+
+void HttpClient::reset() {
+  server_.Clear();
+  request().clear(true);
+  response().clear(true);
+  context_.reset();
+  redirects_ = 0;
+  base_.abort(HE_OPERATION_CANCELLED);
+}
+
+void HttpClient::set_server(const SocketAddress& address) {
+  server_ = address;
+  // Setting 'Host' here allows it to be overridden before starting the request,
+  // if necessary.
+  request().setHeader(HH_HOST, HttpAddress(server_, false), true);
+}
+
+StreamInterface* HttpClient::GetDocumentStream() {
+  return base_.GetDocumentStream();
+}
+
+void HttpClient::start() {
+  if (base_.mode() != HM_NONE) {
+    // call reset() to abort an in-progress request
+    ASSERT(false);
+    return;
+  }
+
+  ASSERT(!IsCacheActive());
+
+  if (request().hasHeader(HH_TRANSFER_ENCODING, NULL)) {
+    // Exact size must be known on the client.  Instead of using chunked
+    // encoding, wrap data with auto-caching file or memory stream.
+    ASSERT(false);
+    return;
+  }
+
+  attempt_ = 0;
+
+  // If no content has been specified, using length of 0.
+  request().setHeader(HH_CONTENT_LENGTH, "0", false);
+
+  if (!agent_.empty()) {
+    request().setHeader(HH_USER_AGENT, agent_, false);
+  }
+
+  UriForm uri_form = uri_form_;
+  if (PROXY_HTTPS == proxy_.type) {
+    // Proxies require absolute form
+    uri_form = URI_ABSOLUTE;
+    request().version = HVER_1_0;
+    request().setHeader(HH_PROXY_CONNECTION, "Keep-Alive", false);
+  } else {
+    request().setHeader(HH_CONNECTION, "Keep-Alive", false);
+  }
+
+  if (URI_ABSOLUTE == uri_form) {
+    // Convert to absolute uri form
+    std::string url;
+    if (request().getAbsoluteUri(&url)) {
+      request().path = url;
+    } else {
+      LOG(LS_WARNING) << "Couldn't obtain absolute uri";
+    }
+  } else if (URI_RELATIVE == uri_form) {
+    // Convert to relative uri form
+    std::string host, path;
+    if (request().getRelativeUri(&host, &path)) {
+      request().setHeader(HH_HOST, host);
+      request().path = path;
+    } else {
+      LOG(LS_WARNING) << "Couldn't obtain relative uri";
+    }
+  }
+
+  if ((NULL != cache_) && CheckCache()) {
+    return;
+  }
+
+  connect();
+}
+
+void HttpClient::connect() {
+  int stream_err;
+  StreamInterface* stream = pool_->RequestConnectedStream(server_, &stream_err);
+  if (stream == NULL) {
+    ASSERT(0 != stream_err);
+    LOG(LS_ERROR) << "RequestConnectedStream error: " << stream_err;
+    onHttpComplete(HM_CONNECT, HE_CONNECT_FAILED);
+  } else {
+    base_.attach(stream);
+    if (stream->GetState() == SS_OPEN) {
+      base_.send(&transaction_->request);
+    }
+  }
+}
+
+void HttpClient::prepare_get(const std::string& url) {
+  reset();
+  Url<char> purl(url);
+  set_server(SocketAddress(purl.host(), purl.port()));
+  request().verb = HV_GET;
+  request().path = purl.full_path();
+}
+
+void HttpClient::prepare_post(const std::string& url,
+                              const std::string& content_type,
+                              StreamInterface* request_doc) {
+  reset();
+  Url<char> purl(url);
+  set_server(SocketAddress(purl.host(), purl.port()));
+  request().verb = HV_POST;
+  request().path = purl.full_path();
+  request().setContent(content_type, request_doc);
+}
+
+void HttpClient::release() {
+  if (StreamInterface* stream = base_.detach()) {
+    pool_->ReturnConnectedStream(stream);
+  }
+}
+
+bool HttpClient::ShouldRedirect(std::string* location) const {
+  // TODO: Unittest redirection.
+  if ((REDIRECT_NEVER == redirect_action_)
+      || !HttpCodeIsRedirection(response().scode)
+      || !response().hasHeader(HH_LOCATION, location)
+      || (redirects_ >= kMaxRedirects))
+    return false;
+  return (REDIRECT_ALWAYS == redirect_action_)
+         || (HC_SEE_OTHER == response().scode)
+         || (HV_HEAD == request().verb)
+         || (HV_GET == request().verb);
+}
+
+bool HttpClient::BeginCacheFile() {
+  ASSERT(NULL != cache_);
+  ASSERT(CS_READY == cache_state_);
+
+  std::string id = GetCacheID(request());
+  CacheLock lock(cache_, id, true);
+  if (!lock.IsLocked()) {
+    LOG_F(LS_WARNING) << "Couldn't lock cache";
+    return false;
+  }
+
+  if (HE_NONE != WriteCacheHeaders(id)) {
+    return false;
+  }
+
+  scoped_ptr<StreamInterface> stream(cache_->WriteResource(id, kCacheBody));
+  if (!stream.get()) {
+    LOG_F(LS_ERROR) << "Couldn't open body cache";
+    return false;
+  }
+  lock.Commit();
+
+  // Let's secretly replace the response document with Folgers Crystals,
+  // er, StreamTap, so that we can mirror the data to our cache.
+  StreamInterface* output = response().document.release();
+  if (!output) {
+    output = new NullStream;
+  }
+  StreamTap* tap = new StreamTap(output, stream.release());
+  response().document.reset(tap);
+  return true;
+}
+
+HttpError HttpClient::WriteCacheHeaders(const std::string& id) {
+  scoped_ptr<StreamInterface> stream(cache_->WriteResource(id, kCacheHeader));
+  if (!stream.get()) {
+    LOG_F(LS_ERROR) << "Couldn't open header cache";
+    return HE_CACHE;
+  }
+
+  if (!HttpWriteCacheHeaders(&transaction_->response, stream.get(), NULL)) {
+    LOG_F(LS_ERROR) << "Couldn't write header cache";
+    return HE_CACHE;
+  }
+
+  return HE_NONE;
+}
+
+void HttpClient::CompleteCacheFile() {
+  // Restore previous response document
+  StreamTap* tap = static_cast<StreamTap*>(response().document.release());
+  response().document.reset(tap->Detach());
+
+  int error;
+  StreamResult result = tap->GetTapResult(&error);
+
+  // Delete the tap and cache stream (which completes cache unlock)
+  delete tap;
+
+  if (SR_SUCCESS != result) {
+    LOG(LS_ERROR) << "Cache file error: " << error;
+    cache_->DeleteResource(GetCacheID(request()));
+  }
+}
+
+bool HttpClient::CheckCache() {
+  ASSERT(NULL != cache_);
+  ASSERT(CS_READY == cache_state_);
+
+  std::string id = GetCacheID(request());
+  if (!cache_->HasResource(id)) {
+    // No cache file available
+    return false;
+  }
+
+  HttpError error = ReadCacheHeaders(id, true);
+
+  if (HE_NONE == error) {
+    switch (HttpGetCacheState(*transaction_)) {
+    case HCS_FRESH:
+      // Cache content is good, read from cache
+      break;
+    case HCS_STALE:
+      // Cache content may be acceptable.  Issue a validation request.
+      if (PrepareValidate()) {
+        return false;
+      }
+      // Couldn't validate, fall through.
+    case HCS_NONE:
+      // Cache content is not useable.  Issue a regular request.
+      response().clear(false);
+      return false;
+    }
+  }
+
+  if (HE_NONE == error) {
+    error = ReadCacheBody(id);
+    cache_state_ = CS_READY;
+  }
+
+  if (HE_CACHE == error) {
+    LOG_F(LS_WARNING) << "Cache failure, continuing with normal request";
+    response().clear(false);
+    return false;
+  }
+
+  SignalHttpClientComplete(this, error);
+  return true;
+}
+
+HttpError HttpClient::ReadCacheHeaders(const std::string& id, bool override) {
+  scoped_ptr<StreamInterface> stream(cache_->ReadResource(id, kCacheHeader));
+  if (!stream.get()) {
+    return HE_CACHE;
+  }
+
+  HttpData::HeaderCombine combine =
+    override ? HttpData::HC_REPLACE : HttpData::HC_AUTO;
+
+  if (!HttpReadCacheHeaders(stream.get(), &transaction_->response, combine)) {
+    LOG_F(LS_ERROR) << "Error reading cache headers";
+    return HE_CACHE;
+  }
+
+  response().scode = HC_OK;
+  return HE_NONE;
+}
+
+HttpError HttpClient::ReadCacheBody(const std::string& id) {
+  cache_state_ = CS_READING;
+
+  HttpError error = HE_NONE;
+
+  size_t data_size;
+  scoped_ptr<StreamInterface> stream(cache_->ReadResource(id, kCacheBody));
+  if (!stream.get() || !stream->GetAvailable(&data_size)) {
+    LOG_F(LS_ERROR) << "Unavailable cache body";
+    error = HE_CACHE;
+  } else {
+    error = OnHeaderAvailable(false, false, data_size);
+  }
+
+  if ((HE_NONE == error)
+      && (HV_HEAD != request().verb)
+      && (NULL != response().document.get())) {
+    char buffer[1024 * 64];
+    StreamResult result = Flow(stream.get(), buffer, ARRAY_SIZE(buffer),
+                               response().document.get());
+    if (SR_SUCCESS != result) {
+      error = HE_STREAM;
+    }
+  }
+
+  return error;
+}
+
+bool HttpClient::PrepareValidate() {
+  ASSERT(CS_READY == cache_state_);
+  // At this point, request() contains the pending request, and response()
+  // contains the cached response headers.  Reformat the request to validate
+  // the cached content.
+  HttpValidatorStrength vs_required = HttpRequestValidatorLevel(request());
+  HttpValidatorStrength vs_available = HttpResponseValidatorLevel(response());
+  if (vs_available < vs_required) {
+    return false;
+  }
+  std::string value;
+  if (response().hasHeader(HH_ETAG, &value)) {
+    request().addHeader(HH_IF_NONE_MATCH, value);
+  }
+  if (response().hasHeader(HH_LAST_MODIFIED, &value)) {
+    request().addHeader(HH_IF_MODIFIED_SINCE, value);
+  }
+  response().clear(false);
+  cache_state_ = CS_VALIDATING;
+  return true;
+}
+
+HttpError HttpClient::CompleteValidate() {
+  ASSERT(CS_VALIDATING == cache_state_);
+
+  std::string id = GetCacheID(request());
+
+  // Merge cached headers with new headers
+  HttpError error = ReadCacheHeaders(id, false);
+  if (HE_NONE != error) {
+    // Rewrite merged headers to cache
+    CacheLock lock(cache_, id);
+    error = WriteCacheHeaders(id);
+  }
+  if (HE_NONE != error) {
+    error = ReadCacheBody(id);
+  }
+  return error;
+}
+
+HttpError HttpClient::OnHeaderAvailable(bool ignore_data, bool chunked,
+                                        size_t data_size) {
+  // If we are ignoring the data, this is an intermediate header.
+  // TODO: don't signal intermediate headers.  Instead, do all header-dependent
+  // processing now, and either set up the next request, or fail outright.
+  // TODO: by default, only write response documents with a success code.
+  SignalHeaderAvailable(this, !ignore_data, ignore_data ? 0 : data_size);
+  if (!ignore_data && !chunked && (data_size != SIZE_UNKNOWN)
+      && response().document.get()) {
+    // Attempt to pre-allocate space for the downloaded data.
+    if (!response().document->ReserveSize(data_size)) {
+      return HE_OVERFLOW;
+    }
+  }
+  return HE_NONE;
+}
+
+//
+// HttpBase Implementation
+//
+
+HttpError HttpClient::onHttpHeaderComplete(bool chunked, size_t& data_size) {
+  if (CS_VALIDATING == cache_state_) {
+    if (HC_NOT_MODIFIED == response().scode) {
+      return CompleteValidate();
+    }
+    // Should we remove conditional headers from request?
+    cache_state_ = CS_READY;
+    cache_->DeleteResource(GetCacheID(request()));
+    // Continue processing response as normal
+  }
+
+  ASSERT(!IsCacheActive());
+  if ((request().verb == HV_HEAD) || !HttpCodeHasBody(response().scode)) {
+    // HEAD requests and certain response codes contain no body
+    data_size = 0;
+  }
+  if (ShouldRedirect(NULL)
+      || ((HC_PROXY_AUTHENTICATION_REQUIRED == response().scode)
+          && (PROXY_HTTPS == proxy_.type))) {
+    // We're going to issue another request, so ignore the incoming data.
+    base_.set_ignore_data(true);
+  }
+
+  HttpError error = OnHeaderAvailable(base_.ignore_data(), chunked, data_size);
+  if (HE_NONE != error) {
+    return error;
+  }
+
+  if ((NULL != cache_)
+      && !base_.ignore_data()
+      && HttpShouldCache(*transaction_)) {
+    if (BeginCacheFile()) {
+      cache_state_ = CS_WRITING;
+    }
+  }
+  return HE_NONE;
+}
+
+void HttpClient::onHttpComplete(HttpMode mode, HttpError err) {
+  if (((HE_DISCONNECTED == err) || (HE_CONNECT_FAILED == err)
+       || (HE_SOCKET_ERROR == err))
+      && (HC_INTERNAL_SERVER_ERROR == response().scode)
+      && (attempt_ < retries_)) {
+    // If the response code has not changed from the default, then we haven't
+    // received anything meaningful from the server, so we are eligible for a
+    // retry.
+    ++attempt_;
+    if (request().document.get() && !request().document->Rewind()) {
+      // Unable to replay the request document.
+      err = HE_STREAM;
+    } else {
+      release();
+      connect();
+      return;
+    }
+  } else if (err != HE_NONE) {
+    // fall through
+  } else if (mode == HM_CONNECT) {
+    base_.send(&transaction_->request);
+    return;
+  } else if ((mode == HM_SEND) || HttpCodeIsInformational(response().scode)) {
+    // If you're interested in informational headers, catch
+    // SignalHeaderAvailable.
+    base_.recv(&transaction_->response);
+    return;
+  } else {
+    if (!HttpShouldKeepAlive(response())) {
+      LOG(LS_VERBOSE) << "HttpClient: closing socket";
+      base_.stream()->Close();
+    }
+    std::string location;
+    if (ShouldRedirect(&location)) {
+      Url<char> purl(location);
+      set_server(SocketAddress(purl.host(), purl.port()));
+      request().path = purl.full_path();
+      if (response().scode == HC_SEE_OTHER) {
+        request().verb = HV_GET;
+        request().clearHeader(HH_CONTENT_TYPE);
+        request().clearHeader(HH_CONTENT_LENGTH);
+        request().document.reset();
+      } else if (request().document.get() && !request().document->Rewind()) {
+        // Unable to replay the request document.
+        ASSERT(REDIRECT_ALWAYS == redirect_action_);
+        err = HE_STREAM;
+      }
+      if (err == HE_NONE) {
+        ++redirects_;
+        context_.reset();
+        response().clear(false);
+        release();
+        start();
+        return;
+      }
+    } else if ((HC_PROXY_AUTHENTICATION_REQUIRED == response().scode)
+               && (PROXY_HTTPS == proxy_.type)) {
+      std::string authorization, auth_method;
+      HttpData::const_iterator begin = response().begin(HH_PROXY_AUTHENTICATE);
+      HttpData::const_iterator end = response().end(HH_PROXY_AUTHENTICATE);
+      for (HttpData::const_iterator it = begin; it != end; ++it) {
+        HttpAuthContext *context = context_.get();
+        HttpAuthResult res = HttpAuthenticate(
+          it->second.data(), it->second.size(),
+          proxy_.address,
+          ToString(request().verb), request().path,
+          proxy_.username, proxy_.password,
+          context, authorization, auth_method);
+        context_.reset(context);
+        if (res == HAR_RESPONSE) {
+          request().setHeader(HH_PROXY_AUTHORIZATION, authorization);
+          if (request().document.get() && !request().document->Rewind()) {
+            err = HE_STREAM;
+          } else {
+            // Explicitly do not reset the HttpAuthContext
+            response().clear(false);
+            // TODO: Reuse socket when authenticating?
+            release();
+            start();
+            return;
+          }
+        } else if (res == HAR_IGNORE) {
+          LOG(INFO) << "Ignoring Proxy-Authenticate: " << auth_method;
+          continue;
+        } else {
+          break;
+        }
+      }
+    }
+  }
+  if (CS_WRITING == cache_state_) {
+    CompleteCacheFile();
+    cache_state_ = CS_READY;
+  } else if (CS_READING == cache_state_) {
+    cache_state_ = CS_READY;
+  }
+  release();
+  SignalHttpClientComplete(this, err);
+}
+
+void HttpClient::onHttpClosed(HttpError err) {
+  // This shouldn't occur, since we return the stream to the pool upon command
+  // completion.
+  ASSERT(false);
+}
+
+//////////////////////////////////////////////////////////////////////
+// HttpClientDefault
+//////////////////////////////////////////////////////////////////////
+
+HttpClientDefault::HttpClientDefault(SocketFactory* factory,
+                                     const std::string& agent,
+                                     HttpTransaction* transaction)
+    : ReuseSocketPool(factory ? factory : Thread::Current()->socketserver()),
+      HttpClient(agent, NULL, transaction) {
+  set_pool(this);
+}
+
+//////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
diff --git a/talk/base/httpclient.h b/talk/base/httpclient.h
new file mode 100644
index 0000000..b64e55f
--- /dev/null
+++ b/talk/base/httpclient.h
@@ -0,0 +1,213 @@
+/*
+ * 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_BASE_HTTPCLIENT_H__
+#define TALK_BASE_HTTPCLIENT_H__
+
+#include "talk/base/common.h"
+#include "talk/base/httpbase.h"
+#include "talk/base/proxyinfo.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/socketpool.h"
+
+namespace talk_base {
+
+//////////////////////////////////////////////////////////////////////
+// Client-specific http utilities
+//////////////////////////////////////////////////////////////////////
+
+// Write cache-relevant response headers to output stream.  If size is non-null,
+// it contains the length of the output in bytes.  output may be null if only
+// the length is desired.
+bool HttpWriteCacheHeaders(const HttpResponseData* response,
+                           StreamInterface* output, size_t* size);
+// Read cached headers from a stream, and them merge them into the response
+// object using the specified combine operation.
+bool HttpReadCacheHeaders(StreamInterface* input,
+                          HttpResponseData* response,
+                          HttpData::HeaderCombine combine);
+
+//////////////////////////////////////////////////////////////////////
+// HttpClient
+// Implements an HTTP 1.1 client.
+//////////////////////////////////////////////////////////////////////
+
+class DiskCache;
+class HttpClient;
+class IPNetPool;
+
+// What to do:  Define STRICT_HTTP_ERROR=1 in your makefile.  Use HttpError in
+// your code (HttpErrorType should only be used for code that is shared
+// with groups which have not yet migrated).
+#if STRICT_HTTP_ERROR
+typedef HttpError HttpErrorType;
+#else  // !STRICT_HTTP_ERROR
+typedef int HttpErrorType;
+#endif  // !STRICT_HTTP_ERROR
+
+class HttpClient : private IHttpNotify {
+public:
+  // If HttpRequestData and HttpResponseData objects are provided, they must
+  // be freed by the caller.  Otherwise, an internal object is allocated.
+  HttpClient(const std::string& agent, StreamPool* pool,
+             HttpTransaction* transaction = NULL);
+  virtual ~HttpClient();
+
+  void set_pool(StreamPool* pool) { pool_ = pool; }
+
+  void set_agent(const std::string& agent) { agent_ = agent; }
+  const std::string& agent() const { return agent_; }
+  
+  void set_proxy(const ProxyInfo& proxy) { proxy_ = proxy; }
+  const ProxyInfo& proxy() const { return proxy_; }
+
+  // Request retries occur when the connection closes before the beginning of
+  // an http response is received.  In these cases, the http server may have
+  // timed out the keepalive connection before it received our request.  Note
+  // that if a request document cannot be rewound, no retry is made.  The
+  // default is 1.
+  void set_request_retries(size_t retries) { retries_ = retries; }
+  size_t request_retries() const { return retries_; }
+
+  enum RedirectAction { REDIRECT_DEFAULT, REDIRECT_ALWAYS, REDIRECT_NEVER };
+  void set_redirect_action(RedirectAction action) { redirect_action_ = action; }
+  RedirectAction redirect_action() const { return redirect_action_; }
+  // Deprecated
+  void set_fail_redirect(bool fail_redirect) {
+    redirect_action_ = REDIRECT_NEVER;
+  }
+  bool fail_redirect() const { return (REDIRECT_NEVER == redirect_action_); }
+
+  enum UriForm { URI_DEFAULT, URI_ABSOLUTE, URI_RELATIVE };
+  void set_uri_form(UriForm form) { uri_form_ = form; }
+  UriForm uri_form() const { return uri_form_; }
+
+  void set_cache(DiskCache* cache) { ASSERT(!IsCacheActive()); cache_ = cache; }
+  bool cache_enabled() const { return (NULL != cache_); }
+
+  // reset clears the server, request, and response structures.  It will also
+  // abort an active request.
+  void reset();
+  
+  void set_server(const SocketAddress& address);
+  const SocketAddress& server() const { return server_; }
+
+  // Note: in order for HttpClient to retry a POST in response to
+  // an authentication challenge, a redirect response, or socket disconnection,
+  // the request document must support 'replaying' by calling Rewind() on it.
+  // In the case where just a subset of a stream should be used as the request
+  // document, the stream may be wrapped with the StreamSegment adapter.
+  HttpTransaction* transaction() { return transaction_; }
+  const HttpTransaction* transaction() const { return transaction_; }
+  HttpRequestData& request() { return transaction_->request; }
+  const HttpRequestData& request() const { return transaction_->request; }
+  HttpResponseData& response() { return transaction_->response; }
+  const HttpResponseData& response() const { return transaction_->response; }
+  
+  // convenience methods
+  void prepare_get(const std::string& url);
+  void prepare_post(const std::string& url, const std::string& content_type,
+                    StreamInterface* request_doc);
+
+  // Convert HttpClient to a pull-based I/O model.
+  StreamInterface* GetDocumentStream();
+
+  // After you finish setting up your request, call start.
+  void start();
+  
+  // Signalled when the header has finished downloading, before the document
+  // content is processed.  You may change the response document in response
+  // to this signal.  The second parameter indicates whether this is an
+  // intermediate (false) or final (true) header.  An intermediate header is
+  // one that generates another request, such as a redirect or authentication
+  // challenge.  The third parameter indicates the length of the response
+  // document, or else SIZE_UNKNOWN.  Note: Do NOT abort the request in response
+  // to this signal.
+  sigslot::signal3<HttpClient*,bool,size_t> SignalHeaderAvailable;
+  // Signalled when the current request finishes.  On success, err is 0.
+  sigslot::signal2<HttpClient*,HttpErrorType> SignalHttpClientComplete;
+
+protected:
+  void connect();
+  void release();
+
+  bool ShouldRedirect(std::string* location) const;
+
+  bool BeginCacheFile();
+  HttpError WriteCacheHeaders(const std::string& id);
+  void CompleteCacheFile();
+
+  bool CheckCache();
+  HttpError ReadCacheHeaders(const std::string& id, bool override);
+  HttpError ReadCacheBody(const std::string& id);
+
+  bool PrepareValidate();
+  HttpError CompleteValidate();
+
+  HttpError OnHeaderAvailable(bool ignore_data, bool chunked, size_t data_size);
+
+  // IHttpNotify Interface
+  virtual HttpError onHttpHeaderComplete(bool chunked, size_t& data_size);
+  virtual void onHttpComplete(HttpMode mode, HttpError err);
+  virtual void onHttpClosed(HttpError err);
+  
+private:
+  enum CacheState { CS_READY, CS_WRITING, CS_READING, CS_VALIDATING };
+  bool IsCacheActive() const { return (cache_state_ > CS_READY); }
+
+  std::string agent_;
+  StreamPool* pool_;
+  HttpBase base_;
+  SocketAddress server_;
+  ProxyInfo proxy_;
+  HttpTransaction* transaction_;
+  bool free_transaction_;
+  size_t retries_, attempt_, redirects_;
+  RedirectAction redirect_action_;
+  UriForm uri_form_;
+  scoped_ptr<HttpAuthContext> context_;
+  DiskCache* cache_;
+  CacheState cache_state_;
+};
+
+//////////////////////////////////////////////////////////////////////
+// HttpClientDefault - Default implementation of HttpClient
+//////////////////////////////////////////////////////////////////////
+
+class HttpClientDefault : public ReuseSocketPool, public HttpClient {
+public:
+  HttpClientDefault(SocketFactory* factory, const std::string& agent,
+                    HttpTransaction* transaction = NULL);
+};
+
+//////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif // TALK_BASE_HTTPCLIENT_H__
diff --git a/talk/base/httpcommon-inl.h b/talk/base/httpcommon-inl.h
new file mode 100644
index 0000000..a33a643
--- /dev/null
+++ b/talk/base/httpcommon-inl.h
@@ -0,0 +1,143 @@
+/*
+ * 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_BASE_HTTPCOMMON_INL_H__
+#define TALK_BASE_HTTPCOMMON_INL_H__
+
+#include "talk/base/common.h"
+#include "talk/base/httpcommon.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// Url
+///////////////////////////////////////////////////////////////////////////////
+
+template<class CTYPE>
+void Url<CTYPE>::do_set_url(const CTYPE* val, size_t len) {
+  if (ascnicmp(val, "http://", 7) == 0) {
+    val += 7; len -= 7;
+    secure_ = false;
+  } else if (ascnicmp(val, "https://", 8) == 0) {
+    val += 8; len -= 8;
+    secure_ = true;
+  } else {
+    clear();
+    return;
+  }
+  const CTYPE* path = strchrn(val, len, static_cast<CTYPE>('/'));
+  if (!path) {
+    path = val + len;
+  }
+  size_t address_length = (path - val);
+  do_set_address(val, address_length);
+  do_set_full_path(path, len - address_length);
+}
+
+template<class CTYPE>
+void Url<CTYPE>::do_set_address(const CTYPE* val, size_t len) {
+  if (const CTYPE* colon = strchrn(val, len, static_cast<CTYPE>(':'))) {
+    host_.assign(val, colon - val);
+    // Note: In every case, we're guaranteed that colon is followed by a null,
+    // or non-numeric character.
+    port_ = static_cast<uint16>(::strtoul(colon + 1, NULL, 10));
+    // TODO: Consider checking for invalid data following port number.
+  } else {
+    host_.assign(val, len);
+    port_ = HttpDefaultPort(secure_);
+  }
+}
+
+template<class CTYPE>
+void Url<CTYPE>::do_set_full_path(const CTYPE* val, size_t len) {
+  const CTYPE* query = strchrn(val, len, static_cast<CTYPE>('?'));
+  if (!query) {
+    query = val + len;
+  }
+  size_t path_length = (query - val);
+  if (0 == path_length) {
+    // TODO: consider failing in this case.
+    path_.assign(1, static_cast<CTYPE>('/'));
+  } else {
+    ASSERT(val[0] == static_cast<CTYPE>('/'));
+    path_.assign(val, path_length);
+  }
+  query_.assign(query, len - path_length);
+}
+
+template<class CTYPE>
+void Url<CTYPE>::do_get_url(string* val) const {
+  CTYPE protocol[9];
+  asccpyn(protocol, ARRAY_SIZE(protocol), secure_ ? "https://" : "http://");
+  val->append(protocol);
+  do_get_address(val);
+  do_get_full_path(val);
+}
+
+template<class CTYPE>
+void Url<CTYPE>::do_get_address(string* val) const {
+  val->append(host_);
+  if (port_ != HttpDefaultPort(secure_)) {
+    CTYPE format[5], port[32];
+    asccpyn(format, ARRAY_SIZE(format), ":%hu");
+    sprintfn(port, ARRAY_SIZE(port), format, port_);
+    val->append(port);
+  }
+}
+
+template<class CTYPE>
+void Url<CTYPE>::do_get_full_path(string* val) const {
+  val->append(path_);
+  val->append(query_);
+}
+
+template<class CTYPE>
+bool Url<CTYPE>::get_attribute(const string& name, string* value) const {
+  if (query_.empty())
+    return false;
+  
+  std::string::size_type pos = query_.find(name, 1);
+  if (std::string::npos == pos)
+    return false;
+
+  pos += name.length() + 1;
+  if ((pos > query_.length()) || (static_cast<CTYPE>('=') != query_[pos-1]))
+    return false;
+
+  std::string::size_type end = query_.find(static_cast<CTYPE>('&'), pos);
+  if (std::string::npos == end) {
+    end = query_.length();
+  }
+  value->assign(query_.substr(pos, end - pos));
+  return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_HTTPCOMMON_INL_H__
diff --git a/talk/base/httpcommon.cc b/talk/base/httpcommon.cc
new file mode 100644
index 0000000..f7553b2
--- /dev/null
+++ b/talk/base/httpcommon.cc
@@ -0,0 +1,1054 @@
+/*
+ * 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 <time.h>
+
+#ifdef WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#define SECURITY_WIN32
+#include <security.h>
+#endif
+
+#include "talk/base/httpcommon-inl.h"
+
+#include "talk/base/base64.h"
+#include "talk/base/common.h"
+#include "talk/base/cryptstring.h"
+#include "talk/base/httpcommon.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/stringdigest.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
+
+namespace talk_base {
+
+#ifdef WIN32
+extern const ConstantLabel SECURITY_ERRORS[];
+#endif
+
+//////////////////////////////////////////////////////////////////////
+// Enum - TODO: expose globally later?
+//////////////////////////////////////////////////////////////////////
+
+bool find_string(size_t& index, const std::string& needle,
+                 const char* const haystack[], size_t max_index) {
+  for (index=0; index<max_index; ++index) {
+	if (_stricmp(needle.c_str(), haystack[index]) == 0) {
+	  return true;
+	}
+  }
+  return false;
+}
+
+template<class E>
+struct Enum {
+  static const char** Names;
+  static size_t Size;
+
+  static inline const char* Name(E val) { return Names[val]; }
+  static inline bool Parse(E& val, const std::string& name) {
+	size_t index;
+	if (!find_string(index, name, Names, Size))
+	  return false;
+	val = static_cast<E>(index);
+	return true;
+  }
+
+  E val;
+
+  inline operator E&() { return val; }
+  inline Enum& operator=(E rhs) { val = rhs; return *this; }
+
+  inline const char* name() const { return Name(val); }
+  inline bool assign(const std::string& name) { return Parse(val, name); }
+  inline Enum& operator=(const std::string& rhs) { assign(rhs); return *this; }
+};
+
+#define ENUM(e,n) \
+  template<> const char** Enum<e>::Names = n; \
+  template<> size_t Enum<e>::Size = sizeof(n)/sizeof(n[0])
+
+//////////////////////////////////////////////////////////////////////
+// HttpCommon
+//////////////////////////////////////////////////////////////////////
+
+static const char* kHttpVersions[HVER_LAST+1] = {
+  "1.0", "1.1", "Unknown"
+};
+ENUM(HttpVersion, kHttpVersions);
+
+static const char* kHttpVerbs[HV_LAST+1] = {
+  "GET", "POST", "PUT", "DELETE", "CONNECT", "HEAD"
+};
+ENUM(HttpVerb, kHttpVerbs);
+
+static const char* kHttpHeaders[HH_LAST+1] = {
+  "Age",
+  "Cache-Control",
+  "Connection",
+  "Content-Disposition",
+  "Content-Length",
+  "Content-Range",
+  "Content-Type",
+  "Cookie",
+  "Date",
+  "ETag",
+  "Expires",
+  "Host",
+  "If-Modified-Since",
+  "If-None-Match",
+  "Keep-Alive",
+  "Last-Modified",
+  "Location",
+  "Proxy-Authenticate",
+  "Proxy-Authorization",
+  "Proxy-Connection",
+  "Range",
+  "Set-Cookie",
+  "TE",
+  "Trailers",
+  "Transfer-Encoding",
+  "Upgrade",
+  "User-Agent",
+  "WWW-Authenticate",
+};
+ENUM(HttpHeader, kHttpHeaders);
+
+const char* ToString(HttpVersion version) {
+  return Enum<HttpVersion>::Name(version);
+}
+
+bool FromString(HttpVersion& version, const std::string& str) {
+  return Enum<HttpVersion>::Parse(version, str);
+}
+
+const char* ToString(HttpVerb verb) {
+  return Enum<HttpVerb>::Name(verb);
+}
+
+bool FromString(HttpVerb& verb, const std::string& str) {
+  return Enum<HttpVerb>::Parse(verb, str);
+}
+
+const char* ToString(HttpHeader header) {
+  return Enum<HttpHeader>::Name(header);
+}
+
+bool FromString(HttpHeader& header, const std::string& str) {
+  return Enum<HttpHeader>::Parse(header, str);
+}
+
+bool HttpCodeHasBody(uint32 code) {
+  return !HttpCodeIsInformational(code)
+         && (code != HC_NO_CONTENT) && (code != HC_NOT_MODIFIED);
+}
+
+bool HttpCodeIsCacheable(uint32 code) {
+  switch (code) {
+  case HC_OK:
+  case HC_NON_AUTHORITATIVE:
+  case HC_PARTIAL_CONTENT:
+  case HC_MULTIPLE_CHOICES:
+  case HC_MOVED_PERMANENTLY:
+  case HC_GONE:
+    return true;
+  default:
+    return false;
+  }
+}
+
+bool HttpHeaderIsEndToEnd(HttpHeader header) {
+  switch (header) {
+  case HH_CONNECTION:
+  case HH_KEEP_ALIVE:
+  case HH_PROXY_AUTHENTICATE:
+  case HH_PROXY_AUTHORIZATION:
+  case HH_PROXY_CONNECTION:  // Note part of RFC... this is non-standard header
+  case HH_TE:
+  case HH_TRAILERS:
+  case HH_TRANSFER_ENCODING:
+  case HH_UPGRADE:
+    return false;
+  default:
+    return true;
+  }
+}
+
+bool HttpHeaderIsCollapsible(HttpHeader header) {
+  switch (header) {
+  case HH_SET_COOKIE:
+  case HH_PROXY_AUTHENTICATE:
+  case HH_WWW_AUTHENTICATE:
+    return false;
+  default:
+    return true;
+  }
+}
+
+bool HttpShouldKeepAlive(const HttpData& data) {
+  std::string connection;
+  if ((data.hasHeader(HH_PROXY_CONNECTION, &connection)
+      || data.hasHeader(HH_CONNECTION, &connection))) {
+    return (_stricmp(connection.c_str(), "Keep-Alive") == 0);
+  }
+  return (data.version >= HVER_1_1);
+}
+
+namespace {
+
+inline bool IsEndOfAttributeName(size_t pos, size_t len, const char * data) {
+  if (pos >= len)
+    return true;
+  if (isspace(static_cast<unsigned char>(data[pos])))
+    return true;
+  // The reason for this complexity is that some attributes may contain trailing
+  // equal signs (like base64 tokens in Negotiate auth headers)
+  if ((pos+1 < len) && (data[pos] == '=') &&
+      !isspace(static_cast<unsigned char>(data[pos+1])) &&
+      (data[pos+1] != '=')) {
+    return true;
+  }
+  return false;
+}
+
+// TODO: unittest for EscapeAttribute and HttpComposeAttributes.
+
+std::string EscapeAttribute(const std::string& attribute) {
+  const size_t kMaxLength = attribute.length() * 2 + 1;
+  char* buffer = STACK_ARRAY(char, kMaxLength);
+  size_t len = escape(buffer, kMaxLength, attribute.data(), attribute.length(),
+                      "\"", '\\');
+  return std::string(buffer, len);
+}
+
+}  // anonymous namespace
+
+void HttpComposeAttributes(const HttpAttributeList& attributes, char separator,
+                           std::string* composed) {
+  std::stringstream ss;
+  for (size_t i=0; i<attributes.size(); ++i) {
+    if (i > 0) {
+      ss << separator << " ";
+    }
+    ss << attributes[i].first;
+    if (!attributes[i].second.empty()) {
+      ss << "=\"" << EscapeAttribute(attributes[i].second) << "\"";
+    }
+  }
+  *composed = ss.str();
+}
+
+void HttpParseAttributes(const char * data, size_t len,
+                         HttpAttributeList& attributes) {
+  size_t pos = 0;
+  while (true) {
+    // Skip leading whitespace
+    while ((pos < len) && isspace(static_cast<unsigned char>(data[pos]))) {
+      ++pos;
+    }
+
+    // End of attributes?
+    if (pos >= len)
+      return;
+
+    // Find end of attribute name
+    size_t start = pos;
+    while (!IsEndOfAttributeName(pos, len, data)) {
+      ++pos;
+    }
+
+    HttpAttribute attribute;
+    attribute.first.assign(data + start, data + pos);
+
+    // Attribute has value?
+    if ((pos < len) && (data[pos] == '=')) {
+      ++pos; // Skip '='
+      // Check if quoted value
+      if ((pos < len) && (data[pos] == '"')) {
+        while (++pos < len) {
+          if (data[pos] == '"') {
+            ++pos;
+            break;
+          }
+          if ((data[pos] == '\\') && (pos + 1 < len))
+            ++pos;
+          attribute.second.append(1, data[pos]);
+        }
+      } else {
+        while ((pos < len) &&
+            !isspace(static_cast<unsigned char>(data[pos])) &&
+            (data[pos] != ',')) {
+          attribute.second.append(1, data[pos++]);
+        }
+      }
+    }
+
+    attributes.push_back(attribute);
+    if ((pos < len) && (data[pos] == ',')) ++pos; // Skip ','
+  }
+}
+
+bool HttpHasAttribute(const HttpAttributeList& attributes,
+                      const std::string& name,
+                      std::string* value) {
+  for (HttpAttributeList::const_iterator it = attributes.begin();
+       it != attributes.end(); ++it) {
+    if (it->first == name) {
+      if (value) {
+        *value = it->second;
+      }
+      return true;
+    }
+  }
+  return false;
+}
+
+bool HttpHasNthAttribute(HttpAttributeList& attributes,
+                         size_t index,
+                         std::string* name,
+                         std::string* value) {
+  if (index >= attributes.size())
+    return false;
+
+  if (name)
+    *name = attributes[index].first;
+  if (value)
+    *value = attributes[index].second;
+  return true;
+}
+
+bool HttpDateToSeconds(const std::string& date, unsigned long* seconds) {
+  const char* const kTimeZones[] = {
+    "UT", "GMT", "EST", "EDT", "CST", "CDT", "MST", "MDT", "PST", "PDT",
+    "A", "B", "C", "D", "E", "F", "G", "H", "I", "K", "L", "M",
+    "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y"
+  };
+  const int kTimeZoneOffsets[] = {
+     0,  0, -5, -4, -6, -5, -7, -6, -8, -7,
+    -1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -11, -12,
+     1,  2,  3,  4,  5,  6,  7,  8,  9,  10,  11,  12
+  };
+
+  ASSERT(NULL != seconds);
+  struct tm tval;
+  memset(&tval, 0, sizeof(tval));
+  char month[4], zone[6];
+  memset(month, 0, sizeof(month));
+  memset(zone, 0, sizeof(zone));
+
+  if (7 != sscanf(date.c_str(), "%*3s, %d %3s %d %d:%d:%d %5c",
+                  &tval.tm_mday, month, &tval.tm_year,
+                  &tval.tm_hour, &tval.tm_min, &tval.tm_sec, zone)) {
+    return false;
+  }
+  switch (toupper(month[2])) {
+  case 'N': tval.tm_mon = (month[1] == 'A') ? 0 : 5; break;
+  case 'B': tval.tm_mon = 1; break;
+  case 'R': tval.tm_mon = (month[0] == 'M') ? 2 : 3; break;
+  case 'Y': tval.tm_mon = 4; break;
+  case 'L': tval.tm_mon = 6; break;
+  case 'G': tval.tm_mon = 7; break;
+  case 'P': tval.tm_mon = 8; break;
+  case 'T': tval.tm_mon = 9; break;
+  case 'V': tval.tm_mon = 10; break;
+  case 'C': tval.tm_mon = 11; break;
+  }
+  tval.tm_year -= 1900;
+  unsigned long gmt, non_gmt = mktime(&tval);
+  if ((zone[0] == '+') || (zone[0] == '-')) {
+    if (!isdigit(zone[1]) || !isdigit(zone[2])
+        || !isdigit(zone[3]) || !isdigit(zone[4])) {
+      return false;
+    }
+    int hours = (zone[1] - '0') * 10 + (zone[2] - '0');
+    int minutes = (zone[3] - '0') * 10 + (zone[4] - '0');
+    int offset = (hours * 60 + minutes) * 60;
+    gmt = non_gmt + (zone[0] == '+') ? offset : -offset;
+  } else {
+    size_t zindex;
+    if (!find_string(zindex, zone, kTimeZones, ARRAY_SIZE(kTimeZones))) {
+      return false;
+    }
+    gmt = non_gmt + kTimeZoneOffsets[zindex] * 60 * 60;
+  }
+  // TODO: Android should support timezone, see b/2441195
+#if defined(OSX) || defined(ANDROID) || defined(BSD)
+  tm *tm_for_timezone = localtime((time_t *)&gmt);
+  *seconds = gmt + tm_for_timezone->tm_gmtoff;
+#else
+  *seconds = gmt - timezone;
+#endif
+  return true;
+}
+
+std::string HttpAddress(const SocketAddress& address, bool secure) {
+  return (address.port() == HttpDefaultPort(secure))
+          ? address.hostname() : address.ToString();
+}
+
+//////////////////////////////////////////////////////////////////////
+// HttpData
+//////////////////////////////////////////////////////////////////////
+
+void
+HttpData::clear(bool release_document) {
+  // Clear headers first, since releasing a document may have far-reaching
+  // effects.
+  headers_.clear();
+  if (release_document) {
+    document.reset();
+  }
+}
+
+void
+HttpData::copy(const HttpData& src) {
+  headers_ = src.headers_;
+}
+
+void
+HttpData::changeHeader(const std::string& name, const std::string& value,
+                       HeaderCombine combine) {
+  if (combine == HC_AUTO) {
+    HttpHeader header;
+    // Unrecognized headers are collapsible
+    combine = !FromString(header, name) || HttpHeaderIsCollapsible(header)
+              ? HC_YES : HC_NO;
+  } else if (combine == HC_REPLACE) {
+    headers_.erase(name);
+    combine = HC_NO;
+  }
+  // At this point, combine is one of (YES, NO, NEW)
+  if (combine != HC_NO) {
+    HeaderMap::iterator it = headers_.find(name);
+    if (it != headers_.end()) {
+      if (combine == HC_YES) {
+        it->second.append(",");
+        it->second.append(value);
+	  }
+      return;
+	}
+  }
+  headers_.insert(HeaderMap::value_type(name, value));
+}
+
+size_t HttpData::clearHeader(const std::string& name) {
+  return headers_.erase(name);
+}
+
+HttpData::iterator HttpData::clearHeader(iterator header) {
+  iterator deprecated = header++;
+  headers_.erase(deprecated);
+  return header;
+}
+
+bool
+HttpData::hasHeader(const std::string& name, std::string* value) const {
+  HeaderMap::const_iterator it = headers_.find(name);
+  if (it == headers_.end()) {
+    return false;
+  } else if (value) {
+    *value = it->second;
+  }
+  return true;
+}
+
+void HttpData::setContent(const std::string& content_type,
+                          StreamInterface* document) {
+  setHeader(HH_CONTENT_TYPE, content_type);
+  setDocumentAndLength(document);
+}
+
+void HttpData::setDocumentAndLength(StreamInterface* document) {
+  // TODO: Consider calling Rewind() here?
+  ASSERT(!hasHeader(HH_CONTENT_LENGTH, NULL));
+  ASSERT(!hasHeader(HH_TRANSFER_ENCODING, NULL));
+  ASSERT(document != NULL);
+  this->document.reset(document);
+  size_t content_length = 0;
+  if (this->document->GetAvailable(&content_length)) {
+    char buffer[32];
+    sprintfn(buffer, sizeof(buffer), "%d", content_length);
+    setHeader(HH_CONTENT_LENGTH, buffer);
+  } else {
+    setHeader(HH_TRANSFER_ENCODING, "chunked");
+  }
+}
+
+//
+// HttpRequestData
+//
+
+void
+HttpRequestData::clear(bool release_document) {
+  verb = HV_GET;
+  path.clear();
+  HttpData::clear(release_document);
+}
+
+void
+HttpRequestData::copy(const HttpRequestData& src) {
+  verb = src.verb;
+  path = src.path;
+  HttpData::copy(src);
+}
+
+size_t
+HttpRequestData::formatLeader(char* buffer, size_t size) const {
+  ASSERT(path.find(' ') == std::string::npos);
+  return sprintfn(buffer, size, "%s %.*s HTTP/%s", ToString(verb), path.size(),
+                  path.data(), ToString(version));
+}
+
+HttpError
+HttpRequestData::parseLeader(const char* line, size_t len) {
+  UNUSED(len);
+  unsigned int vmajor, vminor;
+  int vend, dstart, dend;
+  if ((sscanf(line, "%*s%n %n%*s%n HTTP/%u.%u", &vend, &dstart, &dend,
+              &vmajor, &vminor) != 2)
+      || (vmajor != 1)) {
+    return HE_PROTOCOL;
+  }
+  if (vminor == 0) {
+    version = HVER_1_0;
+  } else if (vminor == 1) {
+    version = HVER_1_1;
+  } else {
+    return HE_PROTOCOL;
+  }
+  std::string sverb(line, vend);
+  if (!FromString(verb, sverb.c_str())) {
+    return HE_PROTOCOL; // !?! HC_METHOD_NOT_SUPPORTED?
+  }
+  path.assign(line + dstart, line + dend);
+  return HE_NONE;
+}
+
+bool HttpRequestData::getAbsoluteUri(std::string* uri) const {
+  if (HV_CONNECT == verb)
+    return false;
+  Url<char> url(path);
+  if (url.valid()) {
+    uri->assign(path);
+    return true;
+  }
+  std::string host;
+  if (!hasHeader(HH_HOST, &host))
+    return false;
+  url.set_address(host);
+  url.set_full_path(path);
+  uri->assign(url.url());
+  return url.valid();
+}
+
+bool HttpRequestData::getRelativeUri(std::string* host,
+                                     std::string* path) const
+{
+  if (HV_CONNECT == verb)
+    return false;
+  Url<char> url(this->path);
+  if (url.valid()) {
+    host->assign(url.address());
+    path->assign(url.full_path());
+    return true;
+  }
+  if (!hasHeader(HH_HOST, host))
+    return false;
+  path->assign(this->path);
+  return true;
+}
+
+//
+// HttpResponseData
+//
+
+void
+HttpResponseData::clear(bool release_document) {
+  scode = HC_INTERNAL_SERVER_ERROR;
+  message.clear();
+  HttpData::clear(release_document);
+}
+
+void
+HttpResponseData::copy(const HttpResponseData& src) {
+  scode = src.scode;
+  message = src.message;
+  HttpData::copy(src);
+}
+
+void
+HttpResponseData::set_success(uint32 scode) {
+  this->scode = scode;
+  message.clear();
+  setHeader(HH_CONTENT_LENGTH, "0", false);
+}
+
+void
+HttpResponseData::set_success(const std::string& content_type,
+                              StreamInterface* document,
+                              uint32 scode) {
+  this->scode = scode;
+  message.erase(message.begin(), message.end());
+  setContent(content_type, document);
+}
+
+void
+HttpResponseData::set_redirect(const std::string& location, uint32 scode) {
+  this->scode = scode;
+  message.clear();
+  setHeader(HH_LOCATION, location);
+  setHeader(HH_CONTENT_LENGTH, "0", false);
+}
+
+void
+HttpResponseData::set_error(uint32 scode) {
+  this->scode = scode;
+  message.clear();
+  setHeader(HH_CONTENT_LENGTH, "0", false);
+}
+
+size_t
+HttpResponseData::formatLeader(char* buffer, size_t size) const {
+  size_t len = sprintfn(buffer, size, "HTTP/%s %lu", ToString(version), scode);
+  if (!message.empty()) {
+    len += sprintfn(buffer + len, size - len, " %.*s",
+                    message.size(), message.data());
+  }
+  return len;
+}
+
+HttpError
+HttpResponseData::parseLeader(const char* line, size_t len) {
+  size_t pos = 0;
+  unsigned int vmajor, vminor, temp_scode;
+  int temp_pos;
+  if (sscanf(line, "HTTP %u%n",
+             &temp_scode, &temp_pos) == 1) {
+    // This server's response has no version. :( NOTE: This happens for every
+    // response to requests made from Chrome plugins, regardless of the server's
+    // behaviour.
+    LOG(LS_VERBOSE) << "HTTP version missing from response";
+    version = HVER_UNKNOWN;
+  } else if ((sscanf(line, "HTTP/%u.%u %u%n",
+                     &vmajor, &vminor, &temp_scode, &temp_pos) == 3)
+             && (vmajor == 1)) {
+    // This server's response does have a version.
+    if (vminor == 0) {
+      version = HVER_1_0;
+    } else if (vminor == 1) {
+      version = HVER_1_1;
+    } else {
+      return HE_PROTOCOL;
+    }
+  } else {
+    return HE_PROTOCOL;
+  }
+  scode = temp_scode;
+  pos = static_cast<size_t>(temp_pos);
+  while ((pos < len) && isspace(static_cast<unsigned char>(line[pos]))) ++pos;
+  message.assign(line + pos, len - pos);
+  return HE_NONE;
+}
+
+//////////////////////////////////////////////////////////////////////
+// Http Authentication
+//////////////////////////////////////////////////////////////////////
+
+#define TEST_DIGEST 0
+#if TEST_DIGEST
+/*
+const char * const DIGEST_CHALLENGE =
+  "Digest realm=\"testrealm@host.com\","
+  " qop=\"auth,auth-int\","
+  " nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\","
+  " opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"";
+const char * const DIGEST_METHOD = "GET";
+const char * const DIGEST_URI =
+  "/dir/index.html";;
+const char * const DIGEST_CNONCE =
+  "0a4f113b";
+const char * const DIGEST_RESPONSE =
+  "6629fae49393a05397450978507c4ef1";
+//user_ = "Mufasa";
+//pass_ = "Circle Of Life";
+*/
+const char * const DIGEST_CHALLENGE =
+  "Digest realm=\"Squid proxy-caching web server\","
+  " nonce=\"Nny4QuC5PwiSDixJ\","
+  " qop=\"auth\","
+  " stale=false";
+const char * const DIGEST_URI =
+  "/";
+const char * const DIGEST_CNONCE =
+  "6501d58e9a21cee1e7b5fec894ded024";
+const char * const DIGEST_RESPONSE =
+  "edffcb0829e755838b073a4a42de06bc";
+#endif
+
+std::string quote(const std::string& str) {
+  std::string result;
+  result.push_back('"');
+  for (size_t i=0; i<str.size(); ++i) {
+    if ((str[i] == '"') || (str[i] == '\\'))
+      result.push_back('\\');
+    result.push_back(str[i]);
+  }
+  result.push_back('"');
+  return result;
+}
+
+#ifdef WIN32
+struct NegotiateAuthContext : public HttpAuthContext {
+  CredHandle cred;
+  CtxtHandle ctx;
+  size_t steps;
+  bool specified_credentials;
+
+  NegotiateAuthContext(const std::string& auth, CredHandle c1, CtxtHandle c2)
+  : HttpAuthContext(auth), cred(c1), ctx(c2), steps(0),
+    specified_credentials(false)
+  { }
+
+  virtual ~NegotiateAuthContext() {
+    DeleteSecurityContext(&ctx);
+    FreeCredentialsHandle(&cred);
+  }
+};
+#endif // WIN32
+
+HttpAuthResult HttpAuthenticate(
+  const char * challenge, size_t len,
+  const SocketAddress& server,
+  const std::string& method, const std::string& uri,
+  const std::string& username, const CryptString& password,
+  HttpAuthContext *& context, std::string& response, std::string& auth_method)
+{
+#if TEST_DIGEST
+  challenge = DIGEST_CHALLENGE;
+  len = strlen(challenge);
+#endif
+
+  HttpAttributeList args;
+  HttpParseAttributes(challenge, len, args);
+  HttpHasNthAttribute(args, 0, &auth_method, NULL);
+
+  if (context && (context->auth_method != auth_method))
+    return HAR_IGNORE;
+
+  // BASIC
+  if (_stricmp(auth_method.c_str(), "basic") == 0) {
+    if (context)
+      return HAR_CREDENTIALS; // Bad credentials
+    if (username.empty())
+      return HAR_CREDENTIALS; // Missing credentials
+
+    context = new HttpAuthContext(auth_method);
+
+    // TODO: convert sensitive to a secure buffer that gets securely deleted
+    //std::string decoded = username + ":" + password;
+    size_t len = username.size() + password.GetLength() + 2;
+    char * sensitive = new char[len];
+    size_t pos = strcpyn(sensitive, len, username.data(), username.size());
+    pos += strcpyn(sensitive + pos, len - pos, ":");
+    password.CopyTo(sensitive + pos, true);
+
+    response = auth_method;
+    response.append(" ");
+    // TODO: create a sensitive-source version of Base64::encode
+    response.append(Base64::Encode(sensitive));
+    memset(sensitive, 0, len);
+    delete [] sensitive;
+    return HAR_RESPONSE;
+  }
+
+  // DIGEST
+  if (_stricmp(auth_method.c_str(), "digest") == 0) {
+    if (context)
+      return HAR_CREDENTIALS; // Bad credentials
+    if (username.empty())
+      return HAR_CREDENTIALS; // Missing credentials
+
+    context = new HttpAuthContext(auth_method);
+
+    std::string cnonce, ncount;
+#if TEST_DIGEST
+    method = DIGEST_METHOD;
+    uri    = DIGEST_URI;
+    cnonce = DIGEST_CNONCE;
+#else
+    char buffer[256];
+    sprintf(buffer, "%d", static_cast<int>(time(0)));
+    cnonce = MD5(buffer);
+#endif
+    ncount = "00000001";
+
+    std::string realm, nonce, qop, opaque;
+    HttpHasAttribute(args, "realm", &realm);
+    HttpHasAttribute(args, "nonce", &nonce);
+    bool has_qop = HttpHasAttribute(args, "qop", &qop);
+    bool has_opaque = HttpHasAttribute(args, "opaque", &opaque);
+
+    // TODO: convert sensitive to be secure buffer
+    //std::string A1 = username + ":" + realm + ":" + password;
+    size_t len = username.size() + realm.size() + password.GetLength() + 3;
+    char * sensitive = new char[len];  // A1
+    size_t pos = strcpyn(sensitive, len, username.data(), username.size());
+    pos += strcpyn(sensitive + pos, len - pos, ":");
+    pos += strcpyn(sensitive + pos, len - pos, realm.c_str());
+    pos += strcpyn(sensitive + pos, len - pos, ":");
+    password.CopyTo(sensitive + pos, true);
+
+    std::string A2 = method + ":" + uri;
+    std::string middle;
+    if (has_qop) {
+      qop = "auth";
+      middle = nonce + ":" + ncount + ":" + cnonce + ":" + qop;
+    } else {
+      middle = nonce;
+    }
+    std::string HA1 = MD5(sensitive);
+    memset(sensitive, 0, len);
+    delete [] sensitive;
+    std::string HA2 = MD5(A2);
+    std::string dig_response = MD5(HA1 + ":" + middle + ":" + HA2);
+
+#if TEST_DIGEST
+    ASSERT(strcmp(dig_response.c_str(), DIGEST_RESPONSE) == 0);
+#endif
+
+    std::stringstream ss;
+    ss << auth_method;
+    ss << " username=" << quote(username);
+    ss << ", realm=" << quote(realm);
+    ss << ", nonce=" << quote(nonce);
+    ss << ", uri=" << quote(uri);
+    if (has_qop) {
+      ss << ", qop=" << qop;
+      ss << ", nc="  << ncount;
+      ss << ", cnonce=" << quote(cnonce);
+    }
+    ss << ", response=\"" << dig_response << "\"";
+    if (has_opaque) {
+      ss << ", opaque=" << quote(opaque);
+    }
+    response = ss.str();
+    return HAR_RESPONSE;
+  }
+
+#ifdef WIN32
+#if 1
+  bool want_negotiate = (_stricmp(auth_method.c_str(), "negotiate") == 0);
+  bool want_ntlm = (_stricmp(auth_method.c_str(), "ntlm") == 0);
+  // SPNEGO & NTLM
+  if (want_negotiate || want_ntlm) {
+    const size_t MAX_MESSAGE = 12000, MAX_SPN = 256;
+    char out_buf[MAX_MESSAGE], spn[MAX_SPN];
+
+#if 0 // Requires funky windows versions
+    DWORD len = MAX_SPN;
+    if (DsMakeSpn("HTTP", server.IPAsString().c_str(), NULL, server.port(),
+                  0, &len, spn) != ERROR_SUCCESS) {
+      LOG_F(WARNING) << "(Negotiate) - DsMakeSpn failed";
+      return HAR_IGNORE;
+    }
+#else
+    sprintfn(spn, MAX_SPN, "HTTP/%s", server.ToString().c_str());
+#endif
+
+    SecBuffer out_sec;
+    out_sec.pvBuffer   = out_buf;
+    out_sec.cbBuffer   = sizeof(out_buf);
+    out_sec.BufferType = SECBUFFER_TOKEN;
+
+    SecBufferDesc out_buf_desc;
+    out_buf_desc.ulVersion = 0;
+    out_buf_desc.cBuffers  = 1;
+    out_buf_desc.pBuffers  = &out_sec;
+
+    const ULONG NEG_FLAGS_DEFAULT =
+      //ISC_REQ_ALLOCATE_MEMORY
+      ISC_REQ_CONFIDENTIALITY
+      //| ISC_REQ_EXTENDED_ERROR
+      //| ISC_REQ_INTEGRITY
+      | ISC_REQ_REPLAY_DETECT
+      | ISC_REQ_SEQUENCE_DETECT
+      //| ISC_REQ_STREAM
+      //| ISC_REQ_USE_SUPPLIED_CREDS
+      ;
+
+    ::TimeStamp lifetime;
+    SECURITY_STATUS ret = S_OK;
+    ULONG ret_flags = 0, flags = NEG_FLAGS_DEFAULT;
+
+    bool specify_credentials = !username.empty();
+    size_t steps = 0;
+
+    //uint32 now = Time();
+
+    NegotiateAuthContext * neg = static_cast<NegotiateAuthContext *>(context);
+    if (neg) {
+      const size_t max_steps = 10;
+      if (++neg->steps >= max_steps) {
+        LOG(WARNING) << "AsyncHttpsProxySocket::Authenticate(Negotiate) too many retries";
+        return HAR_ERROR;
+      }
+      steps = neg->steps;
+
+      std::string challenge, decoded_challenge;
+      if (HttpHasNthAttribute(args, 1, &challenge, NULL)
+          && Base64::Decode(challenge, Base64::DO_STRICT,
+                            &decoded_challenge, NULL)) {
+        SecBuffer in_sec;
+        in_sec.pvBuffer   = const_cast<char *>(decoded_challenge.data());
+        in_sec.cbBuffer   = static_cast<unsigned long>(decoded_challenge.size());
+        in_sec.BufferType = SECBUFFER_TOKEN;
+
+        SecBufferDesc in_buf_desc;
+        in_buf_desc.ulVersion = 0;
+        in_buf_desc.cBuffers  = 1;
+        in_buf_desc.pBuffers  = &in_sec;
+
+        ret = InitializeSecurityContextA(&neg->cred, &neg->ctx, spn, flags, 0, SECURITY_NATIVE_DREP, &in_buf_desc, 0, &neg->ctx, &out_buf_desc, &ret_flags, &lifetime);
+        //LOG(INFO) << "$$$ InitializeSecurityContext @ " << TimeSince(now);
+        if (FAILED(ret)) {
+          LOG(LS_ERROR) << "InitializeSecurityContext returned: "
+                      << ErrorName(ret, SECURITY_ERRORS);
+          return HAR_ERROR;
+        }
+      } else if (neg->specified_credentials) {
+        // Try again with default credentials
+        specify_credentials = false;
+        delete context;
+        context = neg = 0;
+      } else {
+        return HAR_CREDENTIALS;
+      }
+    }
+
+    if (!neg) {
+      unsigned char userbuf[256], passbuf[256], domainbuf[16];
+      SEC_WINNT_AUTH_IDENTITY_A auth_id, * pauth_id = 0;
+      if (specify_credentials) {
+        memset(&auth_id, 0, sizeof(auth_id));
+        size_t len = password.GetLength()+1;
+        char * sensitive = new char[len];
+        password.CopyTo(sensitive, true);
+        std::string::size_type pos = username.find('\\');
+        if (pos == std::string::npos) {
+          auth_id.UserLength = static_cast<unsigned long>(
+            _min(sizeof(userbuf) - 1, username.size()));
+          memcpy(userbuf, username.c_str(), auth_id.UserLength);
+          userbuf[auth_id.UserLength] = 0;
+          auth_id.DomainLength = 0;
+          domainbuf[auth_id.DomainLength] = 0;
+          auth_id.PasswordLength = static_cast<unsigned long>(
+            _min(sizeof(passbuf) - 1, password.GetLength()));
+          memcpy(passbuf, sensitive, auth_id.PasswordLength);
+          passbuf[auth_id.PasswordLength] = 0;
+        } else {
+          auth_id.UserLength = static_cast<unsigned long>(
+            _min(sizeof(userbuf) - 1, username.size() - pos - 1));
+          memcpy(userbuf, username.c_str() + pos + 1, auth_id.UserLength);
+          userbuf[auth_id.UserLength] = 0;
+          auth_id.DomainLength = static_cast<unsigned long>(
+            _min(sizeof(domainbuf) - 1, pos));
+          memcpy(domainbuf, username.c_str(), auth_id.DomainLength);
+          domainbuf[auth_id.DomainLength] = 0;
+          auth_id.PasswordLength = static_cast<unsigned long>(
+            _min(sizeof(passbuf) - 1, password.GetLength()));
+          memcpy(passbuf, sensitive, auth_id.PasswordLength);
+          passbuf[auth_id.PasswordLength] = 0;
+        }
+        memset(sensitive, 0, len);
+        delete [] sensitive;
+        auth_id.User = userbuf;
+        auth_id.Domain = domainbuf;
+        auth_id.Password = passbuf;
+        auth_id.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI;
+        pauth_id = &auth_id;
+        LOG(LS_VERBOSE) << "Negotiate protocol: Using specified credentials";
+      } else {
+        LOG(LS_VERBOSE) << "Negotiate protocol: Using default credentials";
+      }
+
+      CredHandle cred;
+      ret = AcquireCredentialsHandleA(0, want_negotiate ? NEGOSSP_NAME_A : NTLMSP_NAME_A, SECPKG_CRED_OUTBOUND, 0, pauth_id, 0, 0, &cred, &lifetime);
+      //LOG(INFO) << "$$$ AcquireCredentialsHandle @ " << TimeSince(now);
+      if (ret != SEC_E_OK) {
+        LOG(LS_ERROR) << "AcquireCredentialsHandle error: "
+                    << ErrorName(ret, SECURITY_ERRORS);
+        return HAR_IGNORE;
+      }
+
+      //CSecBufferBundle<5, CSecBufferBase::FreeSSPI> sb_out;
+
+      CtxtHandle ctx;
+      ret = InitializeSecurityContextA(&cred, 0, spn, flags, 0, SECURITY_NATIVE_DREP, 0, 0, &ctx, &out_buf_desc, &ret_flags, &lifetime);
+      //LOG(INFO) << "$$$ InitializeSecurityContext @ " << TimeSince(now);
+      if (FAILED(ret)) {
+        LOG(LS_ERROR) << "InitializeSecurityContext returned: "
+                    << ErrorName(ret, SECURITY_ERRORS);
+        FreeCredentialsHandle(&cred);
+        return HAR_IGNORE;
+      }
+
+      ASSERT(!context);
+      context = neg = new NegotiateAuthContext(auth_method, cred, ctx);
+      neg->specified_credentials = specify_credentials;
+      neg->steps = steps;
+    }
+
+    if ((ret == SEC_I_COMPLETE_NEEDED) || (ret == SEC_I_COMPLETE_AND_CONTINUE)) {
+      ret = CompleteAuthToken(&neg->ctx, &out_buf_desc);
+      //LOG(INFO) << "$$$ CompleteAuthToken @ " << TimeSince(now);
+      LOG(LS_VERBOSE) << "CompleteAuthToken returned: "
+                      << ErrorName(ret, SECURITY_ERRORS);
+      if (FAILED(ret)) {
+        return HAR_ERROR;
+      }
+    }
+
+    //LOG(INFO) << "$$$ NEGOTIATE took " << TimeSince(now) << "ms";
+
+    std::string decoded(out_buf, out_buf + out_sec.cbBuffer);
+    response = auth_method;
+    response.append(" ");
+    response.append(Base64::Encode(decoded));
+    return HAR_RESPONSE;
+  }
+#endif
+#endif // WIN32
+
+  return HAR_IGNORE;
+}
+
+//////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
diff --git a/talk/base/httpcommon.h b/talk/base/httpcommon.h
new file mode 100644
index 0000000..7e0b9cf
--- /dev/null
+++ b/talk/base/httpcommon.h
@@ -0,0 +1,463 @@
+/*
+ * 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_BASE_HTTPCOMMON_H__
+#define TALK_BASE_HTTPCOMMON_H__
+
+#include <map>
+#include <string>
+#include <vector>
+#include "talk/base/basictypes.h"
+#include "talk/base/common.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/stream.h"
+
+namespace talk_base {
+
+class CryptString;
+class SocketAddress;
+
+//////////////////////////////////////////////////////////////////////
+// Constants
+//////////////////////////////////////////////////////////////////////
+
+enum HttpCode { 
+  HC_OK = 200,
+  HC_NON_AUTHORITATIVE = 203,
+  HC_NO_CONTENT = 204,
+  HC_PARTIAL_CONTENT = 206,
+
+  HC_MULTIPLE_CHOICES = 300,
+  HC_MOVED_PERMANENTLY = 301,
+  HC_FOUND = 302,
+  HC_SEE_OTHER = 303,
+  HC_NOT_MODIFIED = 304,
+  HC_MOVED_TEMPORARILY = 307,
+
+  HC_BAD_REQUEST = 400,
+  HC_UNAUTHORIZED = 401,
+  HC_FORBIDDEN = 403,
+  HC_NOT_FOUND = 404,
+  HC_PROXY_AUTHENTICATION_REQUIRED = 407,
+  HC_GONE = 410,
+
+  HC_INTERNAL_SERVER_ERROR = 500,
+  HC_NOT_IMPLEMENTED = 501,
+  HC_SERVICE_UNAVAILABLE = 503,
+};
+
+enum HttpVersion {
+  HVER_1_0, HVER_1_1, HVER_UNKNOWN,
+  HVER_LAST = HVER_UNKNOWN
+};
+
+enum HttpVerb {
+  HV_GET, HV_POST, HV_PUT, HV_DELETE, HV_CONNECT, HV_HEAD,
+  HV_LAST = HV_HEAD
+};
+
+enum HttpError {
+  HE_NONE,
+  HE_PROTOCOL,            // Received non-valid HTTP data
+  HE_DISCONNECTED,        // Connection closed unexpectedly
+  HE_OVERFLOW,            // Received too much data for internal buffers
+  HE_CONNECT_FAILED,      // The socket failed to connect.
+  HE_SOCKET_ERROR,        // An error occurred on a connected socket
+  HE_SHUTDOWN,            // Http object is being destroyed
+  HE_OPERATION_CANCELLED, // Connection aborted locally
+  HE_AUTH,                // Proxy Authentication Required
+  HE_CERTIFICATE_EXPIRED, // During SSL negotiation
+  HE_STREAM,              // Problem reading or writing to the document
+  HE_CACHE,               // Problem reading from cache
+  HE_DEFAULT
+};
+
+enum HttpHeader {
+  HH_AGE,
+  HH_CACHE_CONTROL,
+  HH_CONNECTION,
+  HH_CONTENT_DISPOSITION,
+  HH_CONTENT_LENGTH,
+  HH_CONTENT_RANGE,
+  HH_CONTENT_TYPE,
+  HH_COOKIE,
+  HH_DATE,
+  HH_ETAG,
+  HH_EXPIRES,
+  HH_HOST,
+  HH_IF_MODIFIED_SINCE,
+  HH_IF_NONE_MATCH,
+  HH_KEEP_ALIVE,
+  HH_LAST_MODIFIED,
+  HH_LOCATION,
+  HH_PROXY_AUTHENTICATE,
+  HH_PROXY_AUTHORIZATION,
+  HH_PROXY_CONNECTION,
+  HH_RANGE,
+  HH_SET_COOKIE,
+  HH_TE,
+  HH_TRAILERS,
+  HH_TRANSFER_ENCODING,
+  HH_UPGRADE,
+  HH_USER_AGENT,
+  HH_WWW_AUTHENTICATE,
+  HH_LAST = HH_WWW_AUTHENTICATE
+};
+
+const uint16 HTTP_DEFAULT_PORT = 80;
+const uint16 HTTP_SECURE_PORT = 443;
+
+//////////////////////////////////////////////////////////////////////
+// Utility Functions
+//////////////////////////////////////////////////////////////////////
+
+inline HttpError mkerr(HttpError err, HttpError def_err = HE_DEFAULT) {
+  return (err != HE_NONE) ? err : def_err;
+}
+
+const char* ToString(HttpVersion version);
+bool FromString(HttpVersion& version, const std::string& str);
+
+const char* ToString(HttpVerb verb);
+bool FromString(HttpVerb& verb, const std::string& str);
+
+const char* ToString(HttpHeader header);
+bool FromString(HttpHeader& header, const std::string& str);
+
+inline bool HttpCodeIsInformational(uint32 code) { return ((code / 100) == 1); }
+inline bool HttpCodeIsSuccessful(uint32 code)    { return ((code / 100) == 2); }
+inline bool HttpCodeIsRedirection(uint32 code)   { return ((code / 100) == 3); }
+inline bool HttpCodeIsClientError(uint32 code)   { return ((code / 100) == 4); }
+inline bool HttpCodeIsServerError(uint32 code)   { return ((code / 100) == 5); }
+
+bool HttpCodeHasBody(uint32 code);
+bool HttpCodeIsCacheable(uint32 code);
+bool HttpHeaderIsEndToEnd(HttpHeader header);
+bool HttpHeaderIsCollapsible(HttpHeader header);
+
+struct HttpData;
+bool HttpShouldKeepAlive(const HttpData& data);
+
+typedef std::pair<std::string, std::string> HttpAttribute;
+typedef std::vector<HttpAttribute> HttpAttributeList;
+void HttpComposeAttributes(const HttpAttributeList& attributes, char separator,
+                           std::string* composed);
+void HttpParseAttributes(const char * data, size_t len, 
+                         HttpAttributeList& attributes);
+bool HttpHasAttribute(const HttpAttributeList& attributes,
+                      const std::string& name,
+                      std::string* value);
+bool HttpHasNthAttribute(HttpAttributeList& attributes,
+                         size_t index, 
+                         std::string* name,
+                         std::string* value);
+
+// Convert RFC1123 date (DoW, DD Mon YYYY HH:MM:SS TZ) to unix timestamp
+bool HttpDateToSeconds(const std::string& date, unsigned long* seconds);
+
+inline uint16 HttpDefaultPort(bool secure) {
+  return secure ? HTTP_SECURE_PORT : HTTP_DEFAULT_PORT;
+}
+
+// Returns the http server notation for a given address
+std::string HttpAddress(const SocketAddress& address, bool secure);
+
+// functional for insensitive std::string compare
+struct iless {
+  bool operator()(const std::string& lhs, const std::string& rhs) const {
+    return (::_stricmp(lhs.c_str(), rhs.c_str()) < 0);
+  }
+};
+
+// put quotes around a string and escape any quotes inside it
+std::string quote(const std::string& str);
+
+//////////////////////////////////////////////////////////////////////
+// Url
+//////////////////////////////////////////////////////////////////////
+
+template<class CTYPE>
+class Url {
+public:
+  typedef typename Traits<CTYPE>::string string;
+
+  // TODO: Implement Encode/Decode
+  static int Encode(const CTYPE* source, CTYPE* destination, size_t len);
+  static int Encode(const string& source, string& destination);
+  static int Decode(const CTYPE* source, CTYPE* destination, size_t len);
+  static int Decode(const string& source, string& destination);
+
+  Url(const string& url) { do_set_url(url.c_str(), url.size()); }
+  Url(const string& path, const string& host, uint16 port = HTTP_DEFAULT_PORT)
+  : host_(host), port_(port), secure_(HTTP_SECURE_PORT == port)
+  { set_full_path(path); }
+
+  bool valid() const { return !host_.empty(); }
+  void clear() {
+    host_.clear();
+    port_ = HTTP_DEFAULT_PORT;
+    secure_ = false;
+    path_.assign(1, static_cast<CTYPE>('/'));
+    query_.clear();
+  }
+
+  void set_url(const string& val) {
+    do_set_url(val.c_str(), val.size());
+  }
+  string url() const {
+    string val; do_get_url(&val); return val;
+  }
+
+  void set_address(const string& val) {
+    do_set_address(val.c_str(), val.size());
+  }
+  string address() const {
+    string val; do_get_address(&val); return val;
+  }
+
+  void set_full_path(const string& val) {
+    do_set_full_path(val.c_str(), val.size());
+  }
+  string full_path() const {
+    string val; do_get_full_path(&val); return val;
+  }
+
+  void set_host(const string& val) { host_ = val; }
+  const string& host() const { return host_; }
+
+  void set_port(uint16 val) { port_ = val; }
+  uint16 port() const { return port_; }
+
+  void set_secure(bool val) { secure_ = val; }
+  bool secure() const { return secure_; }
+
+  void set_path(const string& val) {
+    if (val.empty()) {
+      path_.assign(1, static_cast<CTYPE>('/'));
+    } else {
+      ASSERT(val[0] == static_cast<CTYPE>('/'));
+      path_ = val;
+    }
+  }
+  const string& path() const { return path_; }
+
+  void set_query(const string& val) {
+    ASSERT(val.empty() || (val[0] == static_cast<CTYPE>('?')));
+    query_ = val;
+  }
+  const string& query() const { return query_; }
+
+  bool get_attribute(const string& name, string* value) const;
+
+private:
+  void do_set_url(const CTYPE* val, size_t len);
+  void do_set_address(const CTYPE* val, size_t len);
+  void do_set_full_path(const CTYPE* val, size_t len);
+
+  void do_get_url(string* val) const;
+  void do_get_address(string* val) const;
+  void do_get_full_path(string* val) const;
+
+  string host_, path_, query_;
+  uint16 port_;
+  bool secure_;
+};
+
+//////////////////////////////////////////////////////////////////////
+// HttpData
+//////////////////////////////////////////////////////////////////////
+
+struct HttpData {
+  typedef std::multimap<std::string, std::string, iless> HeaderMap;
+  typedef HeaderMap::const_iterator const_iterator;
+  typedef HeaderMap::iterator iterator;
+
+  HttpVersion version;
+  scoped_ptr<StreamInterface> document;
+
+  HttpData() : version(HVER_1_1) { }
+
+  enum HeaderCombine { HC_YES, HC_NO, HC_AUTO, HC_REPLACE, HC_NEW };
+  void changeHeader(const std::string& name, const std::string& value,
+                    HeaderCombine combine);
+  inline void addHeader(const std::string& name, const std::string& value,
+                        bool append = true) {
+    changeHeader(name, value, append ? HC_AUTO : HC_NO);
+  }
+  inline void setHeader(const std::string& name, const std::string& value,
+                        bool overwrite = true) {
+    changeHeader(name, value, overwrite ? HC_REPLACE : HC_NEW);
+  }
+  // Returns count of erased headers
+  size_t clearHeader(const std::string& name);
+  // Returns iterator to next header
+  iterator clearHeader(iterator header);
+
+  // keep in mind, this may not do what you want in the face of multiple headers
+  bool hasHeader(const std::string& name, std::string* value) const;
+
+  inline const_iterator begin() const {
+    return headers_.begin();
+  }
+  inline const_iterator end() const {
+    return headers_.end();
+  }
+  inline iterator begin() {
+    return headers_.begin();
+  }
+  inline iterator end() {
+    return headers_.end();
+  }
+  inline const_iterator begin(const std::string& name) const {
+    return headers_.lower_bound(name);
+  }
+  inline const_iterator end(const std::string& name) const {
+    return headers_.upper_bound(name);
+  }
+  inline iterator begin(const std::string& name) {
+    return headers_.lower_bound(name);
+  }
+  inline iterator end(const std::string& name) {
+    return headers_.upper_bound(name);
+  }
+
+  // Convenience methods using HttpHeader
+  inline void changeHeader(HttpHeader header, const std::string& value,
+                           HeaderCombine combine) {
+    changeHeader(ToString(header), value, combine);
+  }
+  inline void addHeader(HttpHeader header, const std::string& value,
+                        bool append = true) {
+    addHeader(ToString(header), value, append);
+  }
+  inline void setHeader(HttpHeader header, const std::string& value,
+                        bool overwrite = true) {
+    setHeader(ToString(header), value, overwrite);
+  }
+  inline void clearHeader(HttpHeader header) {
+    clearHeader(ToString(header));
+  }
+  inline bool hasHeader(HttpHeader header, std::string* value) const {
+    return hasHeader(ToString(header), value);
+  }
+  inline const_iterator begin(HttpHeader header) const {
+    return headers_.lower_bound(ToString(header));
+  }
+  inline const_iterator end(HttpHeader header) const {
+    return headers_.upper_bound(ToString(header));
+  }
+  inline iterator begin(HttpHeader header) {
+    return headers_.lower_bound(ToString(header));
+  }
+  inline iterator end(HttpHeader header) {
+    return headers_.upper_bound(ToString(header));
+  }
+
+  void setContent(const std::string& content_type, StreamInterface* document);
+  void setDocumentAndLength(StreamInterface* document);
+
+  virtual size_t formatLeader(char* buffer, size_t size) const = 0;
+  virtual HttpError parseLeader(const char* line, size_t len) = 0;
+
+protected:  
+  virtual ~HttpData() { }
+  void clear(bool release_document);
+  void copy(const HttpData& src);
+
+private:
+  HeaderMap headers_;
+};
+
+struct HttpRequestData : public HttpData {
+  HttpVerb verb;
+  std::string path;
+
+  HttpRequestData() : verb(HV_GET) { }
+
+  void clear(bool release_document);
+  void copy(const HttpRequestData& src);
+
+  virtual size_t formatLeader(char* buffer, size_t size) const;
+  virtual HttpError parseLeader(const char* line, size_t len);
+
+  bool getAbsoluteUri(std::string* uri) const;
+  bool getRelativeUri(std::string* host, std::string* path) const;
+};
+
+struct HttpResponseData : public HttpData {
+  uint32 scode;
+  std::string message;
+
+  HttpResponseData() : scode(HC_INTERNAL_SERVER_ERROR) { }
+  void clear(bool release_document);
+  void copy(const HttpResponseData& src);
+
+  // Convenience methods
+  void set_success(uint32 scode = HC_OK);
+  void set_success(const std::string& content_type, StreamInterface* document,
+                   uint32 scode = HC_OK);
+  void set_redirect(const std::string& location,
+                    uint32 scode = HC_MOVED_TEMPORARILY);
+  void set_error(uint32 scode);
+
+  virtual size_t formatLeader(char* buffer, size_t size) const;
+  virtual HttpError parseLeader(const char* line, size_t len);
+};
+
+struct HttpTransaction {
+  HttpRequestData request;
+  HttpResponseData response;
+};
+
+//////////////////////////////////////////////////////////////////////
+// Http Authentication
+//////////////////////////////////////////////////////////////////////
+
+struct HttpAuthContext {
+  std::string auth_method;
+  HttpAuthContext(const std::string& auth) : auth_method(auth) { }
+  virtual ~HttpAuthContext() { }
+};
+
+enum HttpAuthResult { HAR_RESPONSE, HAR_IGNORE, HAR_CREDENTIALS, HAR_ERROR };
+
+// 'context' is used by this function to record information between calls.
+// Start by passing a null pointer, then pass the same pointer each additional
+// call.  When the authentication attempt is finished, delete the context.
+HttpAuthResult HttpAuthenticate(
+  const char * challenge, size_t len,
+  const SocketAddress& server,
+  const std::string& method, const std::string& uri,
+  const std::string& username, const CryptString& password,
+  HttpAuthContext *& context, std::string& response, std::string& auth_method);
+
+//////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
+
+#endif // TALK_BASE_HTTPCOMMON_H__
diff --git a/talk/base/httprequest.cc b/talk/base/httprequest.cc
new file mode 100644
index 0000000..48c924e
--- /dev/null
+++ b/talk/base/httprequest.cc
@@ -0,0 +1,127 @@
+/*
+ * libjingle
+ * Copyright 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 "talk/base/httprequest.h"
+
+#include "talk/base/common.h"
+#include "talk/base/firewallsocketserver.h"
+#include "talk/base/httpclient.h"
+#include "talk/base/logging.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/socketadapters.h"
+#include "talk/base/socketpool.h"
+#include "talk/base/ssladapter.h"
+
+using namespace talk_base;
+
+///////////////////////////////////////////////////////////////////////////////
+// HttpMonitor
+///////////////////////////////////////////////////////////////////////////////
+
+HttpMonitor::HttpMonitor(SocketServer *ss) {
+  ASSERT(Thread::Current() != NULL);
+  ss_ = ss;
+  reset();
+}
+
+void HttpMonitor::Connect(HttpClient *http) {
+  http->SignalHttpClientComplete.connect(this,
+    &HttpMonitor::OnHttpClientComplete);
+}
+
+void HttpMonitor::OnHttpClientComplete(HttpClient * http, HttpErrorType error) {
+  complete_ = true;
+  error_ = error;
+  ss_->WakeUp();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// HttpRequest
+///////////////////////////////////////////////////////////////////////////////
+
+const int kDefaultHTTPTimeout = 30 * 1000; // 30 sec
+
+HttpRequest::HttpRequest(const std::string &user_agent)
+    : firewall_(0), port_(80), secure_(false),
+      timeout_(kDefaultHTTPTimeout), fail_redirect_(false),
+      client_(user_agent.c_str(), NULL), error_(HE_NONE) {
+}
+
+void HttpRequest::Send() {
+  // TODO: Rewrite this to use the thread's native socket server, and a more
+  // natural flow?
+
+  PhysicalSocketServer physical;
+  SocketServer * ss = &physical;
+  if (firewall_) {
+    ss = new FirewallSocketServer(ss, firewall_);
+  }
+
+  SslSocketFactory factory(ss, client_.agent());
+  factory.SetProxy(proxy_);
+  if (secure_)
+    factory.UseSSL(host_.c_str());
+
+  //factory.SetLogging("HttpRequest");
+
+  ReuseSocketPool pool(&factory);
+  client_.set_pool(&pool);
+
+  bool transparent_proxy = (port_ == 80) && ((proxy_.type == PROXY_HTTPS) ||
+                           (proxy_.type == PROXY_UNKNOWN));
+
+  if (transparent_proxy) {
+    client_.set_proxy(proxy_);
+  }
+  client_.set_fail_redirect(fail_redirect_);
+
+  SocketAddress server(host_, port_);
+  client_.set_server(server);
+
+  LOG(LS_INFO) << "HttpRequest start: " << host_ + client_.request().path;
+
+  HttpMonitor monitor(ss);
+  monitor.Connect(&client_);
+  client_.start();
+  ss->Wait(timeout_, true);
+  if (!monitor.done()) {
+    LOG(LS_INFO) << "HttpRequest request timed out";
+    client_.reset();
+    return;
+  }
+
+  set_error(monitor.error());
+  if (error_) {
+    LOG(LS_INFO) << "HttpRequest request error: " << error_;
+    return;
+  }
+
+  std::string value;
+  if (client_.response().hasHeader(HH_LOCATION, &value)) {
+    response_redirect_ = value.c_str();
+  }
+}
diff --git a/talk/base/httprequest.h b/talk/base/httprequest.h
new file mode 100644
index 0000000..2e13c32
--- /dev/null
+++ b/talk/base/httprequest.h
@@ -0,0 +1,132 @@
+/*
+ * libjingle
+ * Copyright 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 _HTTPREQUEST_H_
+#define _HTTPREQUEST_H_
+
+#include "talk/base/httpclient.h"
+#include "talk/base/logging.h"
+#include "talk/base/proxyinfo.h"
+#include "talk/base/socketserver.h"
+#include "talk/base/thread.h"
+#include "talk/base/sslsocketfactory.h"  // Deprecated include
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// HttpRequest
+///////////////////////////////////////////////////////////////////////////////
+
+class FirewallManager;
+class MemoryStream;
+
+class HttpRequest {
+public:
+  HttpRequest(const std::string &user_agent);
+
+  void Send();
+
+  void set_proxy(const ProxyInfo& proxy) {
+    proxy_ = proxy;
+  }
+  void set_firewall(FirewallManager * firewall) {
+    firewall_ = firewall;
+  }
+
+  // The DNS name of the host to connect to.
+  const std::string& host() { return host_; }
+  void set_host(const std::string& host) { host_ = host; }
+
+  // The port to connect to on the target host.
+  int port() { return port_; }
+  void set_port(int port) { port_ = port; }
+
+   // Whether the request should use SSL.
+  bool secure() { return secure_; }
+  void set_secure(bool secure) { secure_ = secure; }
+
+  // Returns the redirect when redirection occurs
+  const std::string& response_redirect() { return response_redirect_; }
+
+  // Time to wait on the download, in ms.  Default is 5000 (5s)
+  int timeout() { return timeout_; }
+  void set_timeout(int timeout) { timeout_ = timeout; }
+
+  // Fail redirects to allow analysis of redirect urls, etc.
+  bool fail_redirect() const { return fail_redirect_; }
+  void set_fail_redirect(bool fail_redirect) { fail_redirect_ = fail_redirect; }
+
+  HttpRequestData& request() { return client_.request(); }
+  HttpResponseData& response() { return client_.response(); }
+  HttpErrorType error() { return error_; }
+
+protected:
+  void set_error(HttpErrorType error) { error_ = error; }
+
+private:
+  ProxyInfo proxy_;
+  FirewallManager * firewall_;
+  std::string host_;
+  int port_;
+  bool secure_;
+  int timeout_;
+  bool fail_redirect_;
+  HttpClient client_;
+  HttpErrorType error_;
+  std::string response_redirect_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// HttpMonitor
+///////////////////////////////////////////////////////////////////////////////
+
+class HttpMonitor : public sigslot::has_slots<> {
+public:
+  HttpMonitor(SocketServer *ss);
+
+  void reset() {
+    complete_ = false;
+    error_ = HE_DEFAULT;
+  }
+
+  bool done() const { return complete_; }
+  HttpErrorType error() const { return error_; }
+
+  void Connect(HttpClient* http);
+  void OnHttpClientComplete(HttpClient * http, HttpErrorType error);
+
+private:
+  bool complete_;
+  HttpErrorType error_;
+  SocketServer *ss_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base_
+
+#endif  // _HTTPREQUEST_H_
diff --git a/talk/base/latebindingsymboltable.cc b/talk/base/latebindingsymboltable.cc
new file mode 100644
index 0000000..f9d59ab
--- /dev/null
+++ b/talk/base/latebindingsymboltable.cc
@@ -0,0 +1,111 @@
+/*
+ * libjingle
+ * Copyright 2004--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 "talk/base/latebindingsymboltable.h"
+
+#ifdef LINUX
+#include <dlfcn.h>
+#endif
+
+#include "talk/base/logging.h"
+
+namespace talk_base {
+
+inline static const char *GetDllError() {
+#ifdef LINUX
+  char *err = dlerror();
+  if (err) {
+    return err;
+  } else {
+    return "No error";
+  }
+#else
+#error Not implemented
+#endif
+}
+
+DllHandle InternalLoadDll(const char dll_name[]) {
+#ifdef LINUX
+  DllHandle handle = dlopen(dll_name, RTLD_NOW);
+#else
+#error Not implemented
+#endif
+  if (handle == kInvalidDllHandle) {
+    LOG(LS_WARNING) << "Can't load " << dll_name << ": " << GetDllError();
+  }
+  return handle;
+}
+
+void InternalUnloadDll(DllHandle handle) {
+#ifdef LINUX
+  if (dlclose(handle) != 0) {
+    LOG(LS_ERROR) << GetDllError();
+  }
+#else
+#error Not implemented
+#endif
+}
+
+static bool LoadSymbol(DllHandle handle,
+                       const char *symbol_name,
+                       void **symbol) {
+#ifdef LINUX
+  *symbol = dlsym(handle, symbol_name);
+  char *err = dlerror();
+  if (err) {
+    LOG(LS_ERROR) << "Error loading symbol " << symbol_name << ": " << err;
+    return false;
+  } else if (!*symbol) {
+    LOG(LS_ERROR) << "Symbol " << symbol_name << " is NULL";
+    return false;
+  }
+  return true;
+#else
+#error Not implemented
+#endif
+}
+
+// This routine MUST assign SOME value for every symbol, even if that value is
+// NULL, or else some symbols may be left with uninitialized data that the
+// caller may later interpret as a valid address.
+bool InternalLoadSymbols(DllHandle handle,
+                         int num_symbols,
+                         const char *const symbol_names[],
+                         void *symbols[]) {
+#ifdef LINUX
+  // Clear any old errors.
+  dlerror();
+#endif
+  for (int i = 0; i < num_symbols; ++i) {
+    if (!LoadSymbol(handle, symbol_names[i], &symbols[i])) {
+      return false;
+    }
+  }
+  return true;
+}
+
+}  // namespace talk_base
diff --git a/talk/base/latebindingsymboltable.h b/talk/base/latebindingsymboltable.h
new file mode 100644
index 0000000..994c26a
--- /dev/null
+++ b/talk/base/latebindingsymboltable.h
@@ -0,0 +1,193 @@
+/*
+ * libjingle
+ * Copyright 2004--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.
+ */
+
+#ifndef TALK_BASE_LATEBINDINGSYMBOLTABLE_H_
+#define TALK_BASE_LATEBINDINGSYMBOLTABLE_H_
+
+#include <stddef.h>  // for NULL
+#include <string.h>
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+
+// This file provides macros for creating "symbol table" classes to simplify the
+// dynamic loading of symbols from DLLs. Currently the implementation only
+// supports Linux and pure C symbols.
+// See talk/sound/pulseaudiosymboltable.(h|cc) for an example.
+
+namespace talk_base {
+
+#ifdef LINUX
+typedef void *DllHandle;
+
+const DllHandle kInvalidDllHandle = NULL;
+#else
+#error Not implemented
+#endif
+
+// These are helpers for use only by the class below.
+DllHandle InternalLoadDll(const char dll_name[]);
+
+void InternalUnloadDll(DllHandle handle);
+
+bool InternalLoadSymbols(DllHandle handle,
+                         int num_symbols,
+                         const char *const symbol_names[],
+                         void *symbols[]);
+
+template <int SYMBOL_TABLE_SIZE,
+          const char kDllName[],
+          const char *const kSymbolNames[]>
+class LateBindingSymbolTable {
+ public:
+  LateBindingSymbolTable()
+      : handle_(kInvalidDllHandle),
+        undefined_symbols_(false) {
+    memset(symbols_, 0, sizeof(symbols_));
+  }
+
+  ~LateBindingSymbolTable() {
+    Unload();
+  }
+
+  static int NumSymbols() {
+    return SYMBOL_TABLE_SIZE;
+  }
+
+  // We do not use this, but we offer it for theoretical convenience.
+  static const char *GetSymbolName(int index) {
+    ASSERT(index < NumSymbols());
+    return kSymbolNames[index];
+  }
+
+  bool IsLoaded() const {
+    return handle_ != kInvalidDllHandle;
+  }
+
+  // Loads the DLL and the symbol table. Returns true iff the DLL and symbol
+  // table loaded successfully.
+  bool Load() {
+    if (IsLoaded()) {
+      return true;
+    }
+    if (undefined_symbols_) {
+      // We do not attempt to load again because repeated attempts are not
+      // likely to succeed and DLL loading is costly.
+      LOG(LS_ERROR) << "We know there are undefined symbols";
+      return false;
+    }
+    handle_ = InternalLoadDll(kDllName);
+    if (!IsLoaded()) {
+      return false;
+    }
+    if (!InternalLoadSymbols(handle_, NumSymbols(), kSymbolNames, symbols_)) {
+      undefined_symbols_ = true;
+      Unload();
+      return false;
+    }
+    return true;
+  }
+
+  void Unload() {
+    if (!IsLoaded()) {
+      return;
+    }
+    InternalUnloadDll(handle_);
+    handle_ = kInvalidDllHandle;
+    memset(symbols_, 0, sizeof(symbols_));
+  }
+
+  // Retrieves the given symbol. NOTE: Recommended to use LATESYM_GET below
+  // instead of this.
+  void *GetSymbol(int index) const {
+    ASSERT(IsLoaded());
+    ASSERT(index < NumSymbols());
+    return symbols_[index];
+  }
+
+ private:
+  DllHandle handle_;
+  bool undefined_symbols_;
+  void *symbols_[SYMBOL_TABLE_SIZE];
+
+  DISALLOW_COPY_AND_ASSIGN(LateBindingSymbolTable);
+};
+
+// This macro must be invoked in a header to declare a symbol table class.
+#define LATE_BINDING_SYMBOL_TABLE_DECLARE_BEGIN(ClassName) \
+enum {
+
+// This macro must be invoked in the header declaration once for each symbol
+// (recommended to use an X-Macro to avoid duplication).
+// This macro defines an enum with names built from the symbols, which
+// essentially creates a hash table in the compiler from symbol names to their
+// indices in the symbol table class.
+#define LATE_BINDING_SYMBOL_TABLE_DECLARE_ENTRY(ClassName, sym) \
+  ClassName##_SYMBOL_TABLE_INDEX_##sym,
+
+// This macro completes the header declaration.
+#define LATE_BINDING_SYMBOL_TABLE_DECLARE_END(ClassName) \
+  ClassName##_SYMBOL_TABLE_SIZE \
+}; \
+\
+extern const char ClassName##_kDllName[]; \
+extern const char *const \
+    ClassName##_kSymbolNames[ClassName##_SYMBOL_TABLE_SIZE]; \
+\
+typedef ::talk_base::LateBindingSymbolTable<ClassName##_SYMBOL_TABLE_SIZE, \
+                                            ClassName##_kDllName, \
+                                            ClassName##_kSymbolNames> \
+    ClassName;
+
+// This macro must be invoked in a .cc file to define a previously-declared
+// symbol table class.
+#define LATE_BINDING_SYMBOL_TABLE_DEFINE_BEGIN(ClassName, dllName) \
+const char ClassName##_kDllName[] = dllName; \
+const char *const ClassName##_kSymbolNames[ClassName##_SYMBOL_TABLE_SIZE] = {
+
+// This macro must be invoked in the .cc definition once for each symbol
+// (recommended to use an X-Macro to avoid duplication).
+// This would have to use the mangled name if we were to ever support C++
+// symbols.
+#define LATE_BINDING_SYMBOL_TABLE_DEFINE_ENTRY(ClassName, sym) \
+  #sym,
+
+#define LATE_BINDING_SYMBOL_TABLE_DEFINE_END(ClassName) \
+};
+
+// Index of a given symbol in the given symbol table class.
+#define LATESYM_INDEXOF(ClassName, sym) \
+  (ClassName##_SYMBOL_TABLE_INDEX_##sym)
+
+// Returns a reference to the given late-binded symbol, with the correct type.
+#define LATESYM_GET(ClassName, inst, sym) \
+  (*reinterpret_cast<typeof(&sym)>( \
+      (inst)->GetSymbol(LATESYM_INDEXOF(ClassName, sym))))
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_LATEBINDINGSYMBOLTABLE_H_
diff --git a/talk/base/linked_ptr.h b/talk/base/linked_ptr.h
new file mode 100644
index 0000000..a98a367
--- /dev/null
+++ b/talk/base/linked_ptr.h
@@ -0,0 +1,142 @@
+/*
+ * 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.
+ */
+
+/*
+ * linked_ptr - simple reference linked pointer
+ * (like reference counting, just using a linked list of the references
+ * instead of their count.)
+ *
+ * The implementation stores three pointers for every linked_ptr, but
+ * does not allocate anything on the free store.
+ */
+
+#ifndef TALK_BASE_LINKED_PTR_H__
+#define TALK_BASE_LINKED_PTR_H__
+
+namespace talk_base {
+
+/* For ANSI-challenged compilers, you may want to #define
+ * NO_MEMBER_TEMPLATES, explicit or mutable */
+#define NO_MEMBER_TEMPLATES
+
+template <class X> class linked_ptr
+{
+public:
+
+#ifndef NO_MEMBER_TEMPLATES
+#   define TEMPLATE_FUNCTION template <class Y>
+    TEMPLATE_FUNCTION friend class linked_ptr<Y>;
+#else
+#   define TEMPLATE_FUNCTION
+    typedef X Y;
+#endif
+
+    typedef X element_type;
+
+    explicit linked_ptr(X* p = 0) throw()
+        : itsPtr(p) {itsPrev = itsNext = this;}
+    ~linked_ptr()
+        {release();}
+    linked_ptr(const linked_ptr& r) throw()
+        {acquire(r);}
+    linked_ptr& operator=(const linked_ptr& r)
+    {
+        if (this != &r) {
+            release();
+            acquire(r);
+        }
+        return *this;
+    }
+
+#ifndef NO_MEMBER_TEMPLATES
+    template <class Y> friend class linked_ptr<Y>;
+    template <class Y> linked_ptr(const linked_ptr<Y>& r) throw()
+        {acquire(r);}
+    template <class Y> linked_ptr& operator=(const linked_ptr<Y>& r)
+    {
+        if (this != &r) {
+            release();
+            acquire(r);
+        }
+        return *this;
+    }
+#endif // NO_MEMBER_TEMPLATES
+
+    X& operator*()  const throw()   {return *itsPtr;}
+    X* operator->() const throw()   {return itsPtr;}
+    X* get()        const throw()   {return itsPtr;}
+    bool unique()   const throw()   {return itsPrev ? itsPrev==this : true;}
+
+private:
+    X*                          itsPtr;
+    mutable const linked_ptr*   itsPrev;
+    mutable const linked_ptr*   itsNext;
+
+    void acquire(const linked_ptr& r) throw()
+    { // insert this to the list
+        itsPtr = r.itsPtr;
+        itsNext = r.itsNext;
+        itsNext->itsPrev = this;
+        itsPrev = &r;
+#ifndef mutable
+        r.itsNext = this;
+#else // for ANSI-challenged compilers
+        (const_cast<linked_ptr<X>*>(&r))->itsNext = this;
+#endif
+    }
+
+#ifndef NO_MEMBER_TEMPLATES
+    template <class Y> void acquire(const linked_ptr<Y>& r) throw()
+    { // insert this to the list
+        itsPtr = r.itsPtr;
+        itsNext = r.itsNext;
+        itsNext->itsPrev = this;
+        itsPrev = &r;
+#ifndef mutable
+        r.itsNext = this;
+#else // for ANSI-challenged compilers
+        (const_cast<linked_ptr<X>*>(&r))->itsNext = this;
+#endif
+    }
+#endif // NO_MEMBER_TEMPLATES
+
+    void release()
+    { // erase this from the list, delete if unique
+        if (unique()) delete itsPtr;
+        else {
+            itsPrev->itsNext = itsNext;
+            itsNext->itsPrev = itsPrev;
+            itsPrev = itsNext = 0;
+        }
+        itsPtr = 0;
+    }
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_LINKED_PTR_H__
+
diff --git a/talk/base/linux.cc b/talk/base/linux.cc
new file mode 100644
index 0000000..5ae103e
--- /dev/null
+++ b/talk/base/linux.cc
@@ -0,0 +1,285 @@
+/*
+ * 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.
+ */
+
+#ifdef LINUX
+#include "talk/base/linux.h"
+
+#include <errno.h>
+#include <sys/utsname.h>
+
+#include <cstdio>
+
+#include "talk/base/stringencode.h"
+
+namespace talk_base {
+
+static const char kCpuInfoFile[] = "/proc/cpuinfo";
+static const char kCpuMaxFreqFile[] =
+    "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq";
+
+ProcCpuInfo::ProcCpuInfo() {
+}
+
+ProcCpuInfo::~ProcCpuInfo() {
+}
+
+bool ProcCpuInfo::LoadFromSystem() {
+  ConfigParser procfs;
+  if (!procfs.Open(kCpuInfoFile)) {
+    return false;
+  }
+  return procfs.Parse(&cpu_info_);
+};
+
+bool ProcCpuInfo::GetNumCpus(int *num) {
+  if (cpu_info_.size() == 0) {
+    return false;
+  }
+  *num = cpu_info_.size();
+  return true;
+}
+
+bool ProcCpuInfo::GetNumPhysicalCpus(int *num) {
+  if (cpu_info_.size() == 0) {
+    return false;
+  }
+  int total_cores = 0;
+  int physical_id_prev = -1;
+  int cpus = static_cast<int>(cpu_info_.size());
+  for (int i = 0; i < cpus; ++i) {
+    int physical_id;
+    if (GetCpuIntValue(i, "physical id", &physical_id)) {
+      if (physical_id != physical_id_prev) {
+        physical_id_prev = physical_id;
+        int cores;
+        if (GetCpuIntValue(i, "cpu cores", &cores)) {
+          total_cores += cores;
+        }
+      }
+    }
+  }
+  return total_cores;
+}
+
+bool ProcCpuInfo::GetCpuStringValue(int cpu_id, const std::string& key,
+                                    std::string *result) {
+  if (cpu_id >= static_cast<int>(cpu_info_.size()))
+    return false;
+  ConfigParser::SimpleMap::iterator iter = cpu_info_[cpu_id].find(key);
+  if (iter == cpu_info_[cpu_id].end()) {
+    return false;
+  }
+  *result = iter->second;
+  return true;
+}
+
+bool ProcCpuInfo::GetCpuIntValue(int cpu_id, const std::string& key,
+                                 int *result) {
+  if (cpu_id >= static_cast<int>(cpu_info_.size())) {
+    return false;
+  }
+  ConfigParser::SimpleMap::iterator iter = cpu_info_[cpu_id].find(key);
+  if (iter == cpu_info_[cpu_id].end()) {
+    return false;
+  }
+  *result = atoi((iter->second).c_str());
+  return true;
+}
+
+ConfigParser::ConfigParser() {}
+
+ConfigParser::~ConfigParser() {}
+
+bool ConfigParser::Open(const std::string& filename) {
+  FileStream *fs = new FileStream();
+  if (!fs->Open(filename, "r")) {
+    return false;
+  }
+  instream_.reset(fs);
+  return true;
+}
+
+void ConfigParser::Attach(StreamInterface* stream) {
+  instream_.reset(stream);
+}
+
+bool ConfigParser::Parse(MapVector *key_val_pairs) {
+  // Parses the file and places the found key-value pairs into key_val_pairs.
+  SimpleMap section;
+  while (ParseSection(&section)) {
+    key_val_pairs->push_back(section);
+    section.clear();
+  }
+  return (!key_val_pairs->empty());
+}
+
+bool ConfigParser::ParseSection(SimpleMap *key_val_pair) {
+  // Parses the next section in the filestream and places the found key-value
+  // pairs into key_val_pair.
+  std::string key, value;
+  while (ParseLine(&key, &value)) {
+    (*key_val_pair)[key] = value;
+  }
+  return (!key_val_pair->empty());
+}
+
+bool ConfigParser::ParseLine(std::string *key, std::string *value) {
+  // Parses the next line in the filestream and places the found key-value
+  // pair into key and val.
+  std::string line;
+  if ((instream_->ReadLine(&line)) == EOF) {
+    return false;
+  }
+  std::vector<std::string> tokens;
+  if (2 != split(line, ':', &tokens)) {
+    return false;
+  }
+  // Removes whitespace at the end of Key name
+  size_t pos = tokens[0].length() - 1;
+  while ((pos > 0) && isspace(tokens[0][pos])) {
+    pos--;
+  }
+  tokens[0].erase(pos + 1);
+  // Removes whitespace at the start of value
+  pos = 0;
+  while (pos < tokens[1].length() && isspace(tokens[1][pos])) {
+    pos++;
+  }
+  tokens[1].erase(0, pos);
+  *key = tokens[0];
+  *value = tokens[1];
+  return true;
+}
+
+static bool ExpectLineFromStream(FileStream *stream,
+                                 std::string *out) {
+  StreamResult res = stream->ReadLine(out);
+  if (res != SR_SUCCESS) {
+    if (res != SR_EOS) {
+      LOG(LS_ERROR) << "Error when reading from stream";
+    } else {
+      LOG(LS_ERROR) << "Incorrect number of lines in stream";
+    }
+    return false;
+  }
+  return true;
+}
+
+static void ExpectEofFromStream(FileStream *stream) {
+  std::string unused;
+  StreamResult res = stream->ReadLine(&unused);
+  if (res == SR_SUCCESS) {
+    LOG(LS_WARNING) << "Ignoring unexpected extra lines from stream";
+  } else if (res != SR_EOS) {
+    LOG(LS_WARNING) << "Error when checking for extra lines from stream";
+  }
+}
+
+// For caching the lsb_release output (reading it invokes a sub-process and
+// hence is somewhat expensive).
+static std::string lsb_release_string;
+static CriticalSection lsb_release_string_critsec;
+
+std::string ReadLinuxLsbRelease() {
+  CritScope cs(&lsb_release_string_critsec);
+  if (!lsb_release_string.empty()) {
+    // Have cached result from previous call.
+    return lsb_release_string;
+  }
+  // No cached result. Run lsb_release and parse output.
+  POpenStream lsb_release_output;
+  if (!lsb_release_output.Open("lsb_release -idrcs", "r")) {
+    LOG_ERR(LS_ERROR) << "Can't run lsb_release";
+    return lsb_release_string;  // empty
+  }
+  // Read in the command's output and build the string.
+  std::ostringstream sstr;
+  std::string line;
+  int wait_status;
+
+  if (!ExpectLineFromStream(&lsb_release_output, &line)) {
+    return lsb_release_string;  // empty
+  }
+  sstr << "DISTRIB_ID=" << line;
+
+  if (!ExpectLineFromStream(&lsb_release_output, &line)) {
+    return lsb_release_string;  // empty
+  }
+  sstr << " DISTRIB_DESCRIPTION=\"" << line << '"';
+
+  if (!ExpectLineFromStream(&lsb_release_output, &line)) {
+    return lsb_release_string;  // empty
+  }
+  sstr << " DISTRIB_RELEASE=" << line;
+
+  if (!ExpectLineFromStream(&lsb_release_output, &line)) {
+    return lsb_release_string;  // empty
+  }
+  sstr << " DISTRIB_CODENAME=" << line;
+
+  // Should not be anything left.
+  ExpectEofFromStream(&lsb_release_output);
+
+  lsb_release_output.Close();
+  wait_status = lsb_release_output.GetWaitStatus();
+  if (wait_status == -1 ||
+      !WIFEXITED(wait_status) ||
+      WEXITSTATUS(wait_status) != 0) {
+    LOG(LS_WARNING) << "Unexpected exit status from lsb_release";
+  }
+
+  lsb_release_string = sstr.str();
+
+  return lsb_release_string;
+}
+
+std::string ReadLinuxUname() {
+  struct utsname buf;
+  if (uname(&buf) < 0) {
+    LOG_ERR(LS_ERROR) << "Can't call uname()";
+    return std::string();
+  }
+  std::ostringstream sstr;
+  sstr << buf.sysname << " "
+       << buf.release << " "
+       << buf.version << " "
+       << buf.machine;
+  return sstr.str();
+}
+
+int ReadCpuMaxFreq() {
+  FileStream fs;
+  std::string str;
+  if (!fs.Open(kCpuMaxFreqFile, "r") || SR_SUCCESS != fs.ReadLine(&str)) {
+    return -1;
+  }
+  return atoi(str.c_str());
+}
+
+}  // namespace talk_base
+
+#endif  // LINUX
diff --git a/talk/base/linux.h b/talk/base/linux.h
new file mode 100644
index 0000000..e56eb95
--- /dev/null
+++ b/talk/base/linux.h
@@ -0,0 +1,129 @@
+/*
+ * 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_BASE_LINUX_H_
+#define TALK_BASE_LINUX_H_
+
+#ifdef LINUX
+#include <string>
+#include <map>
+#include <vector>
+#include "talk/base/stream.h"
+
+namespace talk_base {
+
+//////////////////////////////////////////////////////////////////////////////
+// ConfigParser parses a FileStream of an ".ini."-type format into a map.
+//////////////////////////////////////////////////////////////////////////////
+
+// Sample Usage:
+//   ConfigParser parser;
+//   ConfigParser::MapVector key_val_pairs;
+//   if (parser.Open(inifile) && parser.Parse(&key_val_pairs)) {
+//     for (int section_num=0; i < key_val_pairs.size(); ++section_num) {
+//       std::string val1 = key_val_pairs[section_num][key1];
+//       std::string val2 = key_val_pairs[section_num][key2];
+//       // Do something with valn;
+//     }
+//   }
+
+class ConfigParser {
+ public:
+  typedef std::map<std::string, std::string> SimpleMap;
+  typedef std::vector<SimpleMap> MapVector;
+
+  ConfigParser();
+  virtual ~ConfigParser();
+
+  virtual bool Open(const std::string& filename);
+  virtual void Attach(StreamInterface* stream);
+  virtual bool Parse(MapVector *key_val_pairs);
+  virtual bool ParseSection(SimpleMap *key_val_pair);
+  virtual bool ParseLine(std::string *key, std::string *value);
+
+ private:
+  scoped_ptr<StreamInterface> instream_;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+// ProcCpuInfo reads CPU info from the /proc subsystem on any *NIX platform.
+//////////////////////////////////////////////////////////////////////////////
+
+// Sample Usage:
+//   ProcCpuInfo proc_info;
+//   int no_of_cpu;
+//   if (proc_info.LoadFromSystem()) {
+//      std::string out_str;
+//      proc_info.GetNumCpus(&no_of_cpu);
+//      proc_info.GetCpuStringValue(0, "vendor_id", &out_str);
+//      }
+//   }
+
+class ProcCpuInfo {
+ public:
+  ProcCpuInfo();
+  virtual ~ProcCpuInfo();
+
+  // Reads the proc subsystem's cpu info into memory. If this fails, this
+  // returns false; if it succeeds, it returns true.
+  virtual bool LoadFromSystem();
+
+  // Obtains the number of logical CPU threads and places the value num.
+  virtual bool GetNumCpus(int *num);
+
+  // Obtains the number of physical CPU cores and places the value num.
+  virtual bool GetNumPhysicalCpus(int *num);
+
+  // Looks for the CPU proc item with the given name for the given CPU number
+  // and places the string value in result.
+  virtual bool GetCpuStringValue(int cpu_id, const std::string& key,
+                                 std::string *result);
+
+  // Looks for the CPU proc item with the given name for the given CPU number
+  // and places the int value in result.
+  virtual bool GetCpuIntValue(int cpu_id, const std::string& key,
+                              int *result);
+
+ private:
+  ConfigParser::MapVector cpu_info_;
+};
+
+// Builds a string containing the info from lsb_release on a single line.
+std::string ReadLinuxLsbRelease();
+
+// Returns the output of "uname".
+std::string ReadLinuxUname();
+
+// Returns the content (int) of
+// /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq
+// Returns -1 on error.
+int ReadCpuMaxFreq();
+
+}  // namespace talk_base
+
+#endif  // LINUX
+#endif  // TALK_BASE_LINUX_H_
diff --git a/talk/base/logging.cc b/talk/base/logging.cc
new file mode 100644
index 0000000..3515312
--- /dev/null
+++ b/talk/base/logging.cc
@@ -0,0 +1,612 @@
+/*
+ * 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.
+ */
+
+#ifdef WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#define snprintf _snprintf
+#undef ERROR  // wingdi.h
+#endif
+
+#ifdef OSX
+#include <CoreServices/CoreServices.h>
+#elif defined(ANDROID)
+#include <android/log.h>
+static const char kLibjingle[] = "libjingle";
+// Android has a 1024 limit on log inputs. We use 60 chars as an
+// approx for the header/tag portion.
+// See android/system/core/liblog/logd_write.c
+static const int kMaxLogLineSize = 1024 - 60;
+#endif  // OSX || ANDROID
+
+#include <iostream>
+#include <iomanip>
+#include <vector>
+
+#include "talk/base/logging.h"
+#include "talk/base/stream.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/time.h"
+
+namespace talk_base {
+
+/////////////////////////////////////////////////////////////////////////////
+// Constant Labels
+/////////////////////////////////////////////////////////////////////////////
+
+const char * FindLabel(int value, const ConstantLabel entries[]) {
+  for (int i = 0; entries[i].label; ++i) {
+    if (value == entries[i].value) {
+      return entries[i].label;
+    }
+  }
+  return 0;
+}
+
+std::string ErrorName(int err, const ConstantLabel * err_table) {
+  if (err == 0)
+    return "No error";
+
+  if (err_table != 0) {
+    if (const char * value = FindLabel(err, err_table))
+      return value;
+  }
+
+  char buffer[16];
+  snprintf(buffer, sizeof(buffer), "0x%08x", err);
+  return buffer;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// LogMessage
+/////////////////////////////////////////////////////////////////////////////
+
+const int LogMessage::NO_LOGGING = LS_ERROR + 1;
+
+#if _DEBUG
+static const int LOG_DEFAULT = LS_INFO;
+#else  // !_DEBUG
+static const int LOG_DEFAULT = LogMessage::NO_LOGGING;
+#endif  // !_DEBUG
+
+// Global lock for log subsystem, only needed to serialize access to streams_.
+CriticalSection LogMessage::crit_;
+
+// By default, release builds don't log, debug builds at info level
+int LogMessage::min_sev_ = LOG_DEFAULT;
+int LogMessage::dbg_sev_ = LOG_DEFAULT;
+
+// Don't bother printing context for the ubiquitous INFO log messages
+int LogMessage::ctx_sev_ = LS_WARNING;
+
+// The list of logging streams currently configured.
+// Note: we explicitly do not clean this up, because of the uncertain ordering
+// of destructors at program exit.  Let the person who sets the stream trigger
+// cleanup by setting to NULL, or let it leak (safe at program exit).
+LogMessage::StreamList LogMessage::streams_;
+
+// Boolean options default to false (0)
+bool LogMessage::thread_, LogMessage::timestamp_;
+
+// Program start time
+uint32 LogMessage::start_ = StartTime();
+
+// If we're in diagnostic mode, we'll be explicitly set that way; default=false.
+bool LogMessage::is_diagnostic_mode_ = false;
+
+LogMessage::LogMessage(const char* file, int line, LoggingSeverity sev,
+                       LogErrorContext err_ctx, int err, const char* module)
+    : severity_(sev) {
+  // Android's logging facility keeps track of timestamp and thread.
+#ifndef ANDROID
+  if (timestamp_) {
+    uint32 time = TimeSince(start_);
+    print_stream_ << "[" << std::setfill('0') << std::setw(3) << (time / 1000)
+                  << ":" << std::setw(3) << (time % 1000) << std::setfill(' ')
+                  << "] ";
+  }
+
+  if (thread_) {
+#ifdef WIN32
+    DWORD id = GetCurrentThreadId();
+    print_stream_ << "[" << std::hex << id << std::dec << "] ";
+#endif  // WIN32
+  }
+#endif  // !ANDROID
+
+  if (severity_ >= ctx_sev_) {
+    print_stream_ << Describe(sev) << "(" << DescribeFile(file)
+                  << ":" << line << "): ";
+  }
+
+  if (err_ctx != ERRCTX_NONE) {
+    std::ostringstream tmp;
+    tmp << "[0x" << std::setfill('0') << std::hex << std::setw(8) << err << "]";
+    switch (err_ctx) {
+      case ERRCTX_ERRNO:
+        tmp << " " << strerror(err);
+        break;
+#if WIN32
+      case ERRCTX_HRESULT: {
+        char msgbuf[256];
+        DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM;
+        HMODULE hmod = GetModuleHandleA(module);
+        if (hmod)
+          flags |= FORMAT_MESSAGE_FROM_HMODULE;
+        if (DWORD len = FormatMessageA(
+            flags, hmod, err,
+            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+            msgbuf, sizeof(msgbuf) / sizeof(msgbuf[0]), NULL)) {
+          while ((len > 0) &&
+              isspace(static_cast<unsigned char>(msgbuf[len-1]))) {
+            msgbuf[--len] = 0;
+          }
+          tmp << " " << msgbuf;
+        }
+        break;
+      }
+#endif  // WIN32
+#if OSX
+      case ERRCTX_OSSTATUS: {
+        tmp << " " << nonnull(GetMacOSStatusErrorString(err), "Unknown error");
+        if (const char* desc = GetMacOSStatusCommentString(err)) {
+          tmp << ": " << desc;
+        }
+        break;
+      }
+#endif  // OSX
+      default:
+        break;
+    }
+    extra_ = tmp.str();
+  }
+}
+
+LogMessage::~LogMessage() {
+  if (!extra_.empty())
+    print_stream_ << " : " << extra_;
+  print_stream_ << std::endl;
+
+  const std::string& str = print_stream_.str();
+  if (severity_ >= dbg_sev_) {
+    OutputToDebug(str, severity_);
+  }
+
+  // Must lock streams_ before accessing
+  CritScope cs(&crit_);
+  for (StreamList::iterator it = streams_.begin(); it != streams_.end(); ++it) {
+    if (severity_ >= it->second) {
+      OutputToStream(it->first, str);
+    }
+  }
+}
+
+void LogMessage::LogContext(int min_sev) {
+  ctx_sev_ = min_sev;
+}
+
+void LogMessage::LogThreads(bool on) {
+  thread_ = on;
+}
+
+void LogMessage::LogTimestamps(bool on) {
+  timestamp_ = on;
+}
+
+void LogMessage::ResetTimestamps() {
+  start_ = Time();
+}
+
+void LogMessage::LogToDebug(int min_sev) {
+  dbg_sev_ = min_sev;
+  UpdateMinLogSeverity();
+}
+
+void LogMessage::LogToStream(StreamInterface* stream, int min_sev) {
+  CritScope cs(&crit_);
+  // Discard and delete all previously installed streams
+  for (StreamList::iterator it = streams_.begin(); it != streams_.end(); ++it) {
+    delete it->first;
+  }
+  streams_.clear();
+  // Install the new stream, if specified
+  if (stream) {
+    AddLogToStream(stream, min_sev);
+  }
+}
+
+int LogMessage::GetLogToStream(StreamInterface* stream) {
+  CritScope cs(&crit_);
+  int sev = NO_LOGGING;
+  for (StreamList::iterator it = streams_.begin(); it != streams_.end(); ++it) {
+    if (!stream || stream == it->first) {
+      sev = _min(sev, it->second);
+    }
+  }
+  return sev;
+}
+
+void LogMessage::AddLogToStream(StreamInterface* stream, int min_sev) {
+  CritScope cs(&crit_);
+  streams_.push_back(std::make_pair(stream, min_sev));
+  UpdateMinLogSeverity();
+}
+
+void LogMessage::RemoveLogToStream(StreamInterface* stream) {
+  CritScope cs(&crit_);
+  for (StreamList::iterator it = streams_.begin(); it != streams_.end(); ++it) {
+    if (stream == it->first) {
+      streams_.erase(it);
+      break;
+    }
+  }
+  UpdateMinLogSeverity();
+}
+
+void LogMessage::ConfigureLogging(const char* params, const char* filename) {
+  int current_level = LS_VERBOSE;
+  int debug_level = GetLogToDebug();
+  int file_level = GetLogToStream();
+
+  std::vector<std::string> tokens;
+  tokenize(params, ' ', &tokens);
+
+  for (size_t i = 0; i < tokens.size(); ++i) {
+    if (tokens[i].empty())
+      continue;
+
+    // Logging features
+    if (tokens[i] == "tstamp") {
+      LogTimestamps();
+    } else if (tokens[i] == "thread") {
+      LogThreads();
+
+    // Logging levels
+    } else if (tokens[i] == "sensitive") {
+      current_level = LS_SENSITIVE;
+    } else if (tokens[i] == "verbose") {
+      current_level = LS_VERBOSE;
+    } else if (tokens[i] == "info") {
+      current_level = LS_INFO;
+    } else if (tokens[i] == "warning") {
+      current_level = LS_WARNING;
+    } else if (tokens[i] == "error") {
+      current_level = LS_ERROR;
+    } else if (tokens[i] == "none") {
+      current_level = NO_LOGGING;
+
+    // Logging targets
+    } else if (tokens[i] == "file") {
+      file_level = current_level;
+    } else if (tokens[i] == "debug") {
+      debug_level = current_level;
+    }
+  }
+
+#ifdef WIN32
+  if ((NO_LOGGING != debug_level) && !::IsDebuggerPresent()) {
+    // First, attempt to attach to our parent's console... so if you invoke
+    // from the command line, we'll see the output there.  Otherwise, create
+    // our own console window.
+    // Note: These methods fail if a console already exists, which is fine.
+    bool success = false;
+    typedef BOOL (WINAPI* PFN_AttachConsole)(DWORD);
+    if (HINSTANCE kernel32 = ::LoadLibrary(L"kernel32.dll")) {
+      // AttachConsole is defined on WinXP+.
+      if (PFN_AttachConsole attach_console = reinterpret_cast<PFN_AttachConsole>
+            (::GetProcAddress(kernel32, "AttachConsole"))) {
+        success = (FALSE != attach_console(ATTACH_PARENT_PROCESS));
+      }
+      ::FreeLibrary(kernel32);
+    }
+    if (!success) {
+      ::AllocConsole();
+    }
+  }
+#endif  // WIN32
+
+  scoped_ptr<FileStream> stream;
+  if (NO_LOGGING != file_level) {
+    stream.reset(new FileStream);
+    if (!stream->Open(filename, "wb") || !stream->DisableBuffering()) {
+      stream.reset();
+    }
+  }
+
+  LogToDebug(debug_level);
+  LogToStream(stream.release(), file_level);
+}
+
+int LogMessage::ParseLogSeverity(const std::string& value) {
+  int level = NO_LOGGING;
+  if (value == "LS_SENSITIVE") {
+    level = LS_SENSITIVE;
+  } else if (value == "LS_VERBOSE") {
+    level = LS_VERBOSE;
+  } else if (value == "LS_INFO") {
+    level = LS_INFO;
+  } else if (value == "LS_WARNING") {
+    level = LS_WARNING;
+  } else if (value == "LS_ERROR") {
+    level = LS_ERROR;
+  } else if (isdigit(value[0])) {
+    level = atoi(value.c_str());  // NOLINT
+  }
+  return level;
+}
+
+void LogMessage::UpdateMinLogSeverity() {
+  int min_sev = dbg_sev_;
+  for (StreamList::iterator it = streams_.begin(); it != streams_.end(); ++it) {
+    min_sev = _min(dbg_sev_, it->second);
+  }
+  min_sev_ = min_sev;
+}
+
+const char* LogMessage::Describe(LoggingSeverity sev) {
+  switch (sev) {
+  case LS_SENSITIVE: return "Sensitive";
+  case LS_VERBOSE:   return "Verbose";
+  case LS_INFO:      return "Info";
+  case LS_WARNING:   return "Warning";
+  case LS_ERROR:     return "Error";
+  default:           return "<unknown>";
+  }
+}
+
+const char* LogMessage::DescribeFile(const char* file) {
+  const char* end1 = ::strrchr(file, '/');
+  const char* end2 = ::strrchr(file, '\\');
+  if (!end1 && !end2)
+    return file;
+  else
+    return (end1 > end2) ? end1 + 1 : end2 + 1;
+}
+
+void LogMessage::OutputToDebug(const std::string& str,
+                               LoggingSeverity severity) {
+  bool log_to_stderr = true;
+#if defined(OSX) && (!defined(DEBUG) || defined(NDEBUG))
+  // On the Mac, all stderr output goes to the Console log and causes clutter.
+  // So in opt builds, don't log to stderr unless the user specifically sets
+  // a preference to do so.
+  CFStringRef key = CFStringCreateWithCString(kCFAllocatorDefault,
+                                              "logToStdErr",
+                                              kCFStringEncodingUTF8);
+  CFStringRef domain = CFBundleGetIdentifier(CFBundleGetMainBundle());
+  if (key != NULL && domain != NULL) {
+    Boolean exists_and_is_valid;
+    Boolean should_log =
+        CFPreferencesGetAppBooleanValue(key, domain, &exists_and_is_valid);
+    // If the key doesn't exist or is invalid or is false, we will not log to
+    // stderr.
+    log_to_stderr = exists_and_is_valid && should_log;
+  }
+  if (key != NULL) {
+    CFRelease(key);
+  }
+#endif
+#ifdef WIN32
+  // Always log to the debugger.
+  // Perhaps stderr should be controlled by a preference, as on Mac?
+  OutputDebugStringA(str.c_str());
+  if (log_to_stderr) {
+    // This handles dynamically allocated consoles, too.
+    if (HANDLE error_handle = ::GetStdHandle(STD_ERROR_HANDLE)) {
+      log_to_stderr = false;
+      unsigned long written;  // NOLINT
+      ::WriteFile(error_handle, str.data(), str.size(), &written, 0);
+    }
+  }
+#endif  // WIN32
+#ifdef ANDROID
+  // Android's logging facility uses severity to log messages but we
+  // need to map libjingle's severity levels to Android ones first.
+  // Also write to stderr which maybe available to executable started
+  // from the shell.
+  int prio;
+  switch (severity) {
+    case LS_SENSITIVE:
+      __android_log_write(ANDROID_LOG_INFO, kLibjingle, "SENSITIVE");
+      if (log_to_stderr) {
+        std::cerr << "SENSITIVE";
+        std::cerr.flush();
+      }
+      return;
+    case LS_VERBOSE:
+      prio = ANDROID_LOG_VERBOSE;
+      break;
+    case LS_INFO:
+      prio = ANDROID_LOG_INFO;
+      break;
+    case LS_WARNING:
+      prio = ANDROID_LOG_WARN;
+      break;
+    case LS_ERROR:
+      prio = ANDROID_LOG_ERROR;
+      break;
+    default:
+      prio = ANDROID_LOG_UNKNOWN;
+  }
+
+  int size = str.size();
+  int line = 0;
+  int idx = 0;
+  const int max_lines = size / kMaxLogLineSize + 1;
+  if (max_lines == 1) {
+    __android_log_print(prio, kLibjingle, "%.*s", size, str.c_str());
+  } else {
+    while (size > 0) {
+      const int len = std::min(size, kMaxLogLineSize);
+      // Use the size of the string in the format (str may have \0 in the
+      // middle).
+      __android_log_print(prio, kLibjingle, "[%d/%d] %.*s",
+                          line + 1, max_lines,
+                          len, str.c_str() + idx);
+      idx += len;
+      size -= len;
+      ++line;
+    }
+  }
+#endif  // ANDROID
+  if (log_to_stderr) {
+    std::cerr << str;
+    std::cerr.flush();
+  }
+}
+
+void LogMessage::OutputToStream(StreamInterface* stream,
+                                const std::string& str) {
+  // If write isn't fully successful, what are we going to do, log it? :)
+  stream->WriteAll(str.data(), str.size(), NULL, NULL);
+}
+
+//////////////////////////////////////////////////////////////////////
+// Logging Helpers
+//////////////////////////////////////////////////////////////////////
+
+void LogMultiline(LoggingSeverity level, const char* label, bool input,
+                  const void* data, size_t len, bool hex_mode,
+                  LogMultilineState* state) {
+  if (!LOG_CHECK_LEVEL_V(level))
+    return;
+
+  const char * direction = (input ? " << " : " >> ");
+
+  // NULL data means to flush our count of unprintable characters.
+  if (!data) {
+    if (state && state->unprintable_count_[input]) {
+      LOG_V(level) << label << direction << "## "
+                   << state->unprintable_count_[input]
+                   << " consecutive unprintable ##";
+      state->unprintable_count_[input] = 0;
+    }
+    return;
+  }
+
+  // The ctype classification functions want unsigned chars.
+  const unsigned char* udata = static_cast<const unsigned char*>(data);
+
+  if (hex_mode) {
+    const size_t LINE_SIZE = 24;
+    char hex_line[LINE_SIZE * 9 / 4 + 2], asc_line[LINE_SIZE + 1];
+    while (len > 0) {
+      memset(asc_line, ' ', sizeof(asc_line));
+      memset(hex_line, ' ', sizeof(hex_line));
+      size_t line_len = _min(len, LINE_SIZE);
+      for (size_t i = 0; i < line_len; ++i) {
+        unsigned char ch = udata[i];
+        asc_line[i] = isprint(ch) ? ch : '.';
+        hex_line[i*2 + i/4] = hex_encode(ch >> 4);
+        hex_line[i*2 + i/4 + 1] = hex_encode(ch & 0xf);
+      }
+      asc_line[sizeof(asc_line)-1] = 0;
+      hex_line[sizeof(hex_line)-1] = 0;
+      LOG_V(level) << label << direction
+                   << asc_line << " " << hex_line << " ";
+      udata += line_len;
+      len -= line_len;
+    }
+    return;
+  }
+
+  size_t consecutive_unprintable = state ? state->unprintable_count_[input] : 0;
+
+  const unsigned char* end = udata + len;
+  while (udata < end) {
+    const unsigned char* line = udata;
+    const unsigned char* end_of_line = strchrn<unsigned char>(udata,
+                                                              end - udata,
+                                                              '\n');
+    if (!end_of_line) {
+      udata = end_of_line = end;
+    } else {
+      udata = end_of_line + 1;
+    }
+
+    bool is_printable = true;
+
+    // If we are in unprintable mode, we need to see a line of at least
+    // kMinPrintableLine characters before we'll switch back.
+    const ptrdiff_t kMinPrintableLine = 4;
+    if (consecutive_unprintable && ((end_of_line - line) < kMinPrintableLine)) {
+      is_printable = false;
+    } else {
+      // Determine if the line contains only whitespace and printable
+      // characters.
+      bool is_entirely_whitespace = true;
+      for (const unsigned char* pos = line; pos < end_of_line; ++pos) {
+        if (isspace(*pos))
+          continue;
+        is_entirely_whitespace = false;
+        if (!isprint(*pos)) {
+          is_printable = false;
+          break;
+        }
+      }
+      // Treat an empty line following unprintable data as unprintable.
+      if (consecutive_unprintable && is_entirely_whitespace) {
+        is_printable = false;
+      }
+    }
+    if (!is_printable) {
+      consecutive_unprintable += (udata - line);
+      continue;
+    }
+    // Print out the current line, but prefix with a count of prior unprintable
+    // characters.
+    if (consecutive_unprintable) {
+      LOG_V(level) << label << direction << "## " << consecutive_unprintable
+                  << " consecutive unprintable ##";
+      consecutive_unprintable = 0;
+    }
+    // Strip off trailing whitespace.
+    while ((end_of_line > line) && isspace(*(end_of_line-1))) {
+      --end_of_line;
+    }
+    // Filter out any private data
+    std::string substr(reinterpret_cast<const char*>(line), end_of_line - line);
+    std::string::size_type pos_private = substr.find("Email");
+    if (pos_private == std::string::npos) {
+      pos_private = substr.find("Passwd");
+    }
+    if (pos_private == std::string::npos) {
+      LOG_V(level) << label << direction << substr;
+    } else {
+      LOG_V(level) << label << direction << "## omitted for privacy ##";
+    }
+  }
+
+  if (state) {
+    state->unprintable_count_[input] = consecutive_unprintable;
+  }
+}
+
+//////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
diff --git a/talk/base/logging.h b/talk/base/logging.h
new file mode 100644
index 0000000..ac69b7b
--- /dev/null
+++ b/talk/base/logging.h
@@ -0,0 +1,381 @@
+/*
+ * 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.
+ */
+
+//   LOG(...) an ostream target that can be used to send formatted
+// output to a variety of logging targets, such as debugger console, stderr,
+// file, or any StreamInterface.
+//   The severity level passed as the first argument to the LOGging
+// functions is used as a filter, to limit the verbosity of the logging.
+//   Static members of LogMessage documented below are used to control the
+// verbosity and target of the output.
+//   There are several variations on the LOG macro which facilitate logging
+// of common error conditions, detailed below.
+
+// LOG(sev) logs the given stream at severity "sev", which must be a
+//     compile-time constant of the LoggingSeverity type, without the namespace
+//     prefix.
+// LOG_V(sev) Like LOG(), but sev is a run-time variable of the LoggingSeverity
+//     type (basically, it just doesn't prepend the namespace).
+// LOG_F(sev) Like LOG(), but includes the name of the current function.
+// LOG_GLE(M)(sev [, mod]) attempt to add a string description of the
+//     HRESULT returned by GetLastError.  The "M" variant allows searching of a
+//     DLL's string table for the error description.
+// LOG_ERRNO(sev) attempts to add a string description of an errno-derived
+//     error. errno and associated facilities exist on both Windows and POSIX,
+//     but on Windows they only apply to the C/C++ runtime.
+// LOG_ERR(sev) is an alias for the platform's normal error system, i.e. _GLE on
+//     Windows and _ERRNO on POSIX.
+// (The above three also all have _EX versions that let you specify the error
+// code, rather than using the last one.)
+// LOG_E(sev, ctx, err, ...) logs a detailed error interpreted using the
+//     specified context.
+// LOG_CHECK_LEVEL(sev) (and LOG_CHECK_LEVEL_V(sev)) can be used as a test
+//     before performing expensive or sensitive operations whose sole purpose is
+//     to output logging data at the desired level.
+// Lastly, PLOG(sev, err) is an alias for LOG_ERR_EX.
+
+#ifndef TALK_BASE_LOGGING_H_
+#define TALK_BASE_LOGGING_H_
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"  // NOLINT
+#endif
+
+#include <list>
+#include <sstream>
+#include <string>
+#include <utility>
+#include "talk/base/basictypes.h"
+#include "talk/base/criticalsection.h"
+
+namespace talk_base {
+
+class StreamInterface;
+
+///////////////////////////////////////////////////////////////////////////////
+// ConstantLabel can be used to easily generate string names from constant
+// values.  This can be useful for logging descriptive names of error messages.
+// Usage:
+//   const ConstantLabel LIBRARY_ERRORS[] = {
+//     KLABEL(SOME_ERROR),
+//     KLABEL(SOME_OTHER_ERROR),
+//     ...
+//     LASTLABEL
+//   }
+//
+//   int err = LibraryFunc();
+//   LOG(LS_ERROR) << "LibraryFunc returned: "
+//                 << ErrorName(err, LIBRARY_ERRORS);
+
+struct ConstantLabel { int value; const char * label; };
+#define KLABEL(x) { x, #x }
+#define TLABEL(x, y) { x, y }
+#define LASTLABEL { 0, 0 }
+
+const char * FindLabel(int value, const ConstantLabel entries[]);
+std::string ErrorName(int err, const ConstantLabel* err_table);
+
+//////////////////////////////////////////////////////////////////////
+
+// Note that the non-standard LoggingSeverity aliases exist because they are
+// still in broad use.  The meanings of the levels are:
+//  LS_SENSITIVE: Information which should only be logged with the consent
+//   of the user, due to privacy concerns.
+//  LS_VERBOSE: This level is for data which we do not want to appear in the
+//   normal debug log, but should appear in diagnostic logs.
+//  LS_INFO: Chatty level used in debugging for all sorts of things, the default
+//   in debug builds.
+//  LS_WARNING: Something that may warrant investigation.
+//  LS_ERROR: Something that should not have occurred.
+enum LoggingSeverity { LS_SENSITIVE, LS_VERBOSE, LS_INFO, LS_WARNING, LS_ERROR,
+                       INFO = LS_INFO,
+                       WARNING = LS_WARNING,
+                       LERROR = LS_ERROR };
+
+// LogErrorContext assists in interpreting the meaning of an error value.
+enum LogErrorContext {
+  ERRCTX_NONE,
+  ERRCTX_ERRNO,     // System-local errno
+  ERRCTX_HRESULT,   // Windows HRESULT
+  ERRCTX_OSSTATUS,  // MacOS OSStatus
+
+  // Abbreviations for LOG_E macro
+  ERRCTX_EN = ERRCTX_ERRNO,     // LOG_E(sev, EN, x)
+  ERRCTX_HR = ERRCTX_HRESULT,   // LOG_E(sev, HR, x)
+  ERRCTX_OS = ERRCTX_OSSTATUS,  // LOG_E(sev, OS, x)
+};
+
+class LogMessage {
+ public:
+  static const int NO_LOGGING;
+
+  LogMessage(const char* file, int line, LoggingSeverity sev,
+             LogErrorContext err_ctx = ERRCTX_NONE, int err = 0,
+             const char* module = NULL);
+  ~LogMessage();
+
+  static inline bool Loggable(LoggingSeverity sev) { return (sev >= min_sev_); }
+  std::ostream& stream() { return print_stream_; }
+
+  // These are attributes which apply to all logging channels
+  //  LogContext: Display the file and line number of the message
+  static void LogContext(int min_sev);
+  //  LogThreads: Display the thread identifier of the current thread
+  static void LogThreads(bool on = true);
+  //  LogTimestamps: Display the elapsed time of the program
+  static void LogTimestamps(bool on = true);
+
+  // Timestamps begin with program execution, but can be reset with this
+  // function for measuring the duration of an activity, or to synchronize
+  // timestamps between multiple instances.
+  static void ResetTimestamps();
+
+  // These are the available logging channels
+  //  Debug: Debug console on Windows, otherwise stderr
+  static void LogToDebug(int min_sev);
+  static int GetLogToDebug() { return dbg_sev_; }
+
+  //  Stream: Any non-blocking stream interface.  LogMessage takes ownership of
+  //   the stream. Multiple streams may be specified by using AddLogToStream.
+  //   LogToStream is retained for backwards compatibility; when invoked, it
+  //   will discard any previously set streams and install the specified stream.
+  //   GetLogToStream gets the severity for the specified stream, of if none
+  //   is specified, the minimum stream severity.
+  //   RemoveLogToStream removes the specified stream, without destroying it.
+  static void LogToStream(StreamInterface* stream, int min_sev);
+  static int GetLogToStream(StreamInterface* stream = NULL);
+  static void AddLogToStream(StreamInterface* stream, int min_sev);
+  static void RemoveLogToStream(StreamInterface* stream);
+
+  // Testing against MinLogSeverity allows code to avoid potentially expensive
+  // logging operations by pre-checking the logging level.
+  static int GetMinLogSeverity() { return min_sev_; }
+
+  static void SetDiagnosticMode(bool f) { is_diagnostic_mode_ = f; }
+  static bool IsDiagnosticMode() { return is_diagnostic_mode_; }
+
+  // Parses the provided parameter stream to configure the options above.
+  // Useful for configuring logging from the command line.  If file logging
+  // is enabled, it is output to the specified filename.
+  static void ConfigureLogging(const char* params, const char* filename);
+
+  // Convert the string to a LS_ value; also accept numeric values.
+  static int ParseLogSeverity(const std::string& value);
+
+ private:
+  typedef std::list<std::pair<StreamInterface*, int> > StreamList;
+
+  // Updates min_sev_ appropriately when debug sinks change.
+  static void UpdateMinLogSeverity();
+
+  // These assist in formatting some parts of the debug output.
+  static const char* Describe(LoggingSeverity sev);
+  static const char* DescribeFile(const char* file);
+
+  // These write out the actual log messages.
+  static void OutputToDebug(const std::string& msg, LoggingSeverity severity_);
+  static void OutputToStream(StreamInterface* stream, const std::string& msg);
+
+  // The ostream that buffers the formatted message before output
+  std::ostringstream print_stream_;
+
+  // The severity level of this message
+  LoggingSeverity severity_;
+
+  // String data generated in the constructor, that should be appended to
+  // the message before output.
+  std::string extra_;
+
+  // Global lock for the logging subsystem
+  static CriticalSection crit_;
+
+  // dbg_sev_ is the thresholds for those output targets
+  // min_sev_ is the minimum (most verbose) of those levels, and is used
+  //  as a short-circuit in the logging macros to identify messages that won't
+  //  be logged.
+  // ctx_sev_ is the minimum level at which file context is displayed
+  static int min_sev_, dbg_sev_, ctx_sev_;
+
+  // The output streams and their associated severities
+  static StreamList streams_;
+
+  // Flags for formatting options
+  static bool thread_, timestamp_;
+
+  // The timestamp at which logging started.
+  static uint32 start_;
+
+  // are we in diagnostic mode (as defined by the app)?
+  static bool is_diagnostic_mode_;
+
+  DISALLOW_EVIL_CONSTRUCTORS(LogMessage);
+};
+
+//////////////////////////////////////////////////////////////////////
+// Logging Helpers
+//////////////////////////////////////////////////////////////////////
+
+class LogMultilineState {
+ public:
+  size_t unprintable_count_[2];
+  LogMultilineState() {
+    unprintable_count_[0] = unprintable_count_[1] = 0;
+  }
+};
+
+// When possible, pass optional state variable to track various data across
+// multiple calls to LogMultiline.  Otherwise, pass NULL.
+void LogMultiline(LoggingSeverity level, const char* label, bool input,
+                  const void* data, size_t len, bool hex_mode,
+                  LogMultilineState* state);
+
+//////////////////////////////////////////////////////////////////////
+// Macros which automatically disable logging when LOGGING == 0
+//////////////////////////////////////////////////////////////////////
+
+// If LOGGING is not explicitly defined, default to enabled in debug mode
+#if !defined(LOGGING)
+#if defined(_DEBUG) && !defined(NDEBUG)
+#define LOGGING 1
+#else
+#define LOGGING 0
+#endif
+#endif  // !defined(LOGGING)
+
+#ifndef LOG
+#if LOGGING
+
+// The following non-obvious technique for implementation of a
+// conditional log stream was stolen from google3/base/logging.h.
+
+// This class is used to explicitly ignore values in the conditional
+// logging macros.  This avoids compiler warnings like "value computed
+// is not used" and "statement has no effect".
+
+class LogMessageVoidify {
+ public:
+  LogMessageVoidify() { }
+  // This has to be an operator with a precedence lower than << but
+  // higher than ?:
+  void operator&(std::ostream&) { }
+};
+
+#define LOG_SEVERITY_PRECONDITION(sev) \
+  !(talk_base::LogMessage::Loggable(sev)) \
+    ? (void) 0 \
+    : talk_base::LogMessageVoidify() &
+
+#define LOG(sev) \
+  LOG_SEVERITY_PRECONDITION(talk_base::sev) \
+    talk_base::LogMessage(__FILE__, __LINE__, talk_base::sev).stream()
+
+// The _V version is for when a variable is passed in.  It doesn't do the
+// namespace concatination.
+#define LOG_V(sev) \
+  LOG_SEVERITY_PRECONDITION(sev) \
+    talk_base::LogMessage(__FILE__, __LINE__, sev).stream()
+
+// The _F version prefixes the message with the current function name.
+#if defined(__GNUC__) && defined(_DEBUG)
+#define LOG_F(sev) LOG(sev) << __PRETTY_FUNCTION__ << ": "
+#else
+#define LOG_F(sev) LOG(sev) << __FUNCTION__ << ": "
+#endif
+
+#define LOG_CHECK_LEVEL(sev) \
+  talk_base::LogCheckLevel(talk_base::sev)
+#define LOG_CHECK_LEVEL_V(sev) \
+  talk_base::LogCheckLevel(sev)
+inline bool LogCheckLevel(LoggingSeverity sev) {
+  return (LogMessage::GetMinLogSeverity() <= sev);
+}
+
+#define LOG_E(sev, ctx, err, ...) \
+  LOG_SEVERITY_PRECONDITION(talk_base::sev) \
+    talk_base::LogMessage(__FILE__, __LINE__, talk_base::sev, \
+                          talk_base::ERRCTX_ ## ctx, err , ##__VA_ARGS__) \
+        .stream()
+
+#else  // !LOGGING
+
+// Hopefully, the compiler will optimize away some of this code.
+// Note: syntax of "1 ? (void)0 : LogMessage" was causing errors in g++,
+//   converted to "while (false)"
+#define LOG(sev) \
+  while (false)talk_base:: LogMessage(NULL, 0, talk_base::sev).stream()
+#define LOG_V(sev) \
+  while (false) talk_base::LogMessage(NULL, 0, sev).stream()
+#define LOG_F(sev) LOG(sev) << __FUNCTION__ << ": "
+#define LOG_CHECK_LEVEL(sev) \
+  false
+#define LOG_CHECK_LEVEL_V(sev) \
+  false
+
+#define LOG_E(sev, ctx, err, ...) \
+  while (false) talk_base::LogMessage(__FILE__, __LINE__, talk_base::sev, \
+                          talk_base::ERRCTX_ ## ctx, err , ##__VA_ARGS__) \
+      .stream()
+
+#endif  // !LOGGING
+
+#define LOG_ERRNO_EX(sev, err) \
+  LOG_E(sev, ERRNO, err)
+#define LOG_ERRNO(sev) \
+  LOG_ERRNO_EX(sev, errno)
+
+#ifdef WIN32
+#define LOG_GLE_EX(sev, err) \
+  LOG_E(sev, HRESULT, err)
+#define LOG_GLE(sev) \
+  LOG_GLE_EX(sev, GetLastError())
+#define LOG_GLEM(sev, mod) \
+  LOG_E(sev, HRESULT, GetLastError(), mod)
+#define LOG_ERR_EX(sev, err) \
+  LOG_GLE_EX(sev, err)
+#define LOG_ERR(sev) \
+  LOG_GLE(sev)
+#define LAST_SYSTEM_ERROR \
+  (::GetLastError())
+#elif POSIX
+#define LOG_ERR_EX(sev, err) \
+  LOG_ERRNO_EX(sev, err)
+#define LOG_ERR(sev) \
+  LOG_ERRNO(sev)
+#define LAST_SYSTEM_ERROR \
+  (errno)
+#endif  // WIN32
+
+#define PLOG(sev, err) \
+  LOG_ERR_EX(sev, err)
+
+// TODO(?): Add an "assert" wrapper that logs in the same manner.
+
+#endif  // LOG
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_LOGGING_H_
diff --git a/talk/base/macconversion.cc b/talk/base/macconversion.cc
new file mode 100644
index 0000000..4654e53
--- /dev/null
+++ b/talk/base/macconversion.cc
@@ -0,0 +1,176 @@
+/*
+ * libjingle
+ * Copyright 2004--2009, 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.
+ */
+
+#ifdef OSX
+
+#include <CoreFoundation/CoreFoundation.h>
+
+#include "talk/base/logging.h"
+#include "talk/base/macconversion.h"
+
+bool p_convertHostCFStringRefToCPPString(
+  const CFStringRef cfstr, std::string& cppstr) {
+  bool result = false;
+
+  // First this must be non-null,
+  if (NULL != cfstr) {
+    // it must actually *be* a CFString, and not something just masquerading
+    // as one,
+    if (CFGetTypeID(cfstr) == CFStringGetTypeID()) {
+      // and we must be able to get the characters out of it.
+      // (The cfstr owns this buffer; it came from somewhere else,
+      // so someone else gets to take care of getting rid of the cfstr,
+      // and then this buffer will go away automatically.)
+      unsigned length = CFStringGetLength(cfstr);
+      char* buf = new char[1 + length];
+      if (CFStringGetCString(cfstr, buf, 1 + length, kCFStringEncodingASCII)) {
+        if (strlen(buf) == length) {
+          cppstr.assign(buf);
+          result = true;
+        }
+      }
+      delete [] buf;
+    }
+  }
+
+  return result;
+}
+
+bool p_convertCFNumberToInt(CFNumberRef cfn, int* i) {
+  bool converted = false;
+
+  // It must not be null.
+  if (NULL != cfn) {
+    // It must actually *be* a CFNumber and not something just masquerading
+    // as one.
+    if (CFGetTypeID(cfn) == CFNumberGetTypeID()) {
+      CFNumberType ntype = CFNumberGetType(cfn);
+      switch (ntype) {
+        case kCFNumberSInt8Type:
+          SInt8 sint8;
+          converted = CFNumberGetValue(cfn, ntype, static_cast<void*>(&sint8));
+          if (converted) *i = static_cast<int>(sint8);
+          break;
+        case kCFNumberSInt16Type:
+          SInt16 sint16;
+          converted = CFNumberGetValue(cfn, ntype, static_cast<void*>(&sint16));
+          if (converted) *i = static_cast<int>(sint16);
+          break;
+        case kCFNumberSInt32Type:
+          SInt32 sint32;
+          converted = CFNumberGetValue(cfn, ntype, static_cast<void*>(&sint32));
+          if (converted) *i = static_cast<int>(sint32);
+          break;
+        case kCFNumberSInt64Type:
+          SInt64 sint64;
+          converted = CFNumberGetValue(cfn, ntype, static_cast<void*>(&sint64));
+          if (converted) *i = static_cast<int>(sint64);
+          break;
+        case kCFNumberFloat32Type:
+          Float32 float32;
+          converted = CFNumberGetValue(cfn, ntype,
+                                       static_cast<void*>(&float32));
+          if (converted) *i = static_cast<int>(float32);
+          break;
+        case kCFNumberFloat64Type:
+          Float64 float64;
+          converted = CFNumberGetValue(cfn, ntype,
+                                       static_cast<void*>(&float64));
+          if (converted) *i = static_cast<int>(float64);
+          break;
+        case kCFNumberCharType:
+          char charvalue;
+          converted = CFNumberGetValue(cfn, ntype,
+                                       static_cast<void*>(&charvalue));
+          if (converted) *i = static_cast<int>(charvalue);
+          break;
+        case kCFNumberShortType:
+          short shortvalue;
+          converted = CFNumberGetValue(cfn, ntype,
+                                       static_cast<void*>(&shortvalue));
+          if (converted) *i = static_cast<int>(shortvalue);
+          break;
+        case kCFNumberIntType:
+          int intvalue;
+          converted = CFNumberGetValue(cfn, ntype,
+                                       static_cast<void*>(&intvalue));
+          if (converted) *i = static_cast<int>(intvalue);
+          break;
+        case kCFNumberLongType:
+          long longvalue;
+          converted = CFNumberGetValue(cfn, ntype,
+                     static_cast<void*>(&longvalue));
+          if (converted) *i = static_cast<int>(longvalue);
+          break;
+        case kCFNumberLongLongType:
+          long long llvalue;
+          converted = CFNumberGetValue(cfn, ntype,
+                                       static_cast<void*>(&llvalue));
+          if (converted) *i = static_cast<int>(llvalue);
+          break;
+        case kCFNumberFloatType:
+          float floatvalue;
+          converted = CFNumberGetValue(cfn, ntype,
+                                       static_cast<void*>(&floatvalue));
+          if (converted) *i = static_cast<int>(floatvalue);
+          break;
+        case kCFNumberDoubleType:
+          double doublevalue;
+          converted = CFNumberGetValue(cfn, ntype,
+                                       static_cast<void*>(&doublevalue));
+          if (converted) *i = static_cast<int>(doublevalue);
+          break;
+        case kCFNumberCFIndexType:
+          CFIndex cfindex;
+          converted = CFNumberGetValue(cfn, ntype,
+                                       static_cast<void*>(&cfindex));
+          if (converted) *i = static_cast<int>(cfindex);
+          break;
+        default:
+          LOG(LS_ERROR) << "got unknown type.";
+          break;
+      }
+    }
+  }
+
+  return converted;
+}
+
+bool p_isCFNumberTrue(CFNumberRef cfn) {
+  // We assume it's false until proven otherwise.
+  bool result = false;
+  int asInt;
+  bool converted = p_convertCFNumberToInt(cfn, &asInt);
+
+  if (converted && (0 != asInt)) {
+    result = true;
+  }
+
+  return result;
+}
+
+#endif  // OSX
diff --git a/talk/base/macconversion.h b/talk/base/macconversion.h
new file mode 100644
index 0000000..a401cab
--- /dev/null
+++ b/talk/base/macconversion.h
@@ -0,0 +1,56 @@
+/*
+ * libjingle
+ * Copyright 2004--2009, 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_BASE_MACCONVERSION_H_
+#define TALK_BASE_MACCONVERSION_H_
+
+#ifdef OSX
+
+#include <CoreFoundation/CoreFoundation.h>
+
+#include <string>
+
+// given a CFStringRef, attempt to convert it to a C++ string.
+// returns true if it succeeds, false otherwise.
+// We can safely assume, given our context, that the string is
+// going to be in ASCII, because it will either be an IP address,
+// or a domain name, which is guaranteed to be ASCII-representable.
+bool p_convertHostCFStringRefToCPPString(const CFStringRef cfstr,
+                                         std::string& cppstr);
+
+// Convert the CFNumber to an integer, putting the integer in the location
+// given, and returhing true, if the conversion succeeds.
+// If given a NULL or a non-CFNumber, returns false.
+// This is pretty aggresive about trying to convert to int.
+bool p_convertCFNumberToInt(CFNumberRef cfn, int* i);
+
+// given a CFNumberRef, determine if it represents a true value.
+bool p_isCFNumberTrue(CFNumberRef cfn);
+
+#endif  // OSX
+
+#endif  // TALK_BASE_MACCONVERSION_H_
diff --git a/talk/base/macutils.cc b/talk/base/macutils.cc
new file mode 100644
index 0000000..fefe2b9
--- /dev/null
+++ b/talk/base/macutils.cc
@@ -0,0 +1,220 @@
+/*
+ * libjingle
+ * Copyright 2007--2009, 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 <sstream>
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/macutils.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stringutils.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool ToUtf8(const CFStringRef str16, std::string* str8) {
+  if ((NULL == str16) || (NULL == str8))
+    return false;
+  size_t maxlen = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str16),
+                                                    kCFStringEncodingUTF8)
+                  + 1;
+  scoped_array<char> buffer(new char[maxlen]);
+  if (!buffer.get()
+      || !CFStringGetCString(str16, buffer.get(), maxlen,
+                             kCFStringEncodingUTF8))
+    return false;
+  str8->assign(buffer.get());
+  return true;
+}
+
+bool ToUtf16(const std::string& str8, CFStringRef* str16) {
+  if (NULL == str16)
+    return false;
+  *str16 = CFStringCreateWithBytes(kCFAllocatorDefault,
+                                   reinterpret_cast<const UInt8*>(str8.data()),
+                                   str8.length(), kCFStringEncodingUTF8,
+                                   false);
+  return (NULL != *str16);
+}
+
+void DecodeFourChar(UInt32 fc, std::string* out) {
+  std::stringstream ss;
+  ss << '\'';
+  bool printable = true;
+  for (int i = 3; i >= 0; --i) {
+    char ch = (fc >> (8 * i)) & 0xFF;
+    if (isprint(static_cast<unsigned char>(ch))) {
+      ss << ch;
+    } else {
+      printable = false;
+      break;
+    }
+  }
+  if (printable) {
+    ss << '\'';
+  } else {
+    ss.str("");
+    ss << "0x" << std::hex << fc;
+  }
+  out->append(ss.str());
+}
+
+std::string DecodeEvent(EventRef event) {
+  std::string str;
+  DecodeFourChar(::GetEventClass(event), &str);
+  str.push_back(':');
+  DecodeFourChar(::GetEventKind(event), &str);
+  return str;
+}
+
+static bool GetGestalt(OSType ostype, int* value) {
+  ASSERT(NULL != value);
+  SInt32 native_value;
+  OSStatus result = Gestalt(ostype, &native_value);
+  if (noErr == result) {
+    *value = native_value;
+    return true;
+  }
+  std::string str;
+  DecodeFourChar(ostype, &str);
+  LOG_E(LS_ERROR, OS, result) << "Gestalt(" << str << ")";
+  return false;
+}
+
+bool GetOSVersion(int* major, int* minor, int* bugfix) {
+  ASSERT(major && minor && bugfix);
+  if (!GetGestalt(gestaltSystemVersion, major))
+    return false;
+  if (*major < 0x1040) {
+    *bugfix = *major & 0xF;
+    *minor = (*major >> 4) & 0xF;
+    *major = (*major >> 8);
+    return true;
+  }
+  return GetGestalt(gestaltSystemVersionMajor, major)
+      && GetGestalt(gestaltSystemVersionMinor, minor)
+      && GetGestalt(gestaltSystemVersionBugFix, bugfix);
+}
+
+MacOSVersionName GetOSVersionName() {
+  int major = 0, minor = 0, bugfix = 0;
+  if (!GetOSVersion(&major, &minor, &bugfix))
+    return kMacOSUnknown;
+  if (major > 10)
+    return kMacOSNewer;
+  if ((major < 10) || (minor < 3))
+    return kMacOSOlder;
+  switch (minor) {
+    case 3:
+      return kMacOSPanther;
+    case 4:
+      return kMacOSTiger;
+    case 5:
+      return kMacOSLeopard;
+  }
+  return kMacOSNewer;
+}
+
+bool GetQuickTimeVersion(std::string* out) {
+  int ver;
+  if (!GetGestalt(gestaltQuickTimeVersion, &ver))
+    return false;
+
+  std::stringstream ss;
+  ss << std::hex << ver;
+  *out = ss.str();
+  return true;
+}
+
+bool RunAppleScript(const std::string& script) {
+  ComponentInstance component = NULL;
+  AEDesc script_desc;
+  AEDesc result_data;
+  OSStatus err;
+  OSAID script_id, result_id;
+
+  AECreateDesc(typeNull, NULL, 0, &script_desc);
+  AECreateDesc(typeNull, NULL, 0, &result_data);
+  script_id = kOSANullScript;
+  result_id = kOSANullScript;
+
+  component = OpenDefaultComponent(kOSAComponentType, typeAppleScript);
+  if (component == NULL) {
+    LOG(LS_ERROR) << "Failed opening Apple Script component";
+    return false;
+  }
+  err = AECreateDesc(typeUTF8Text, script.data(), script.size(), &script_desc);
+  if (err != noErr) {
+    CloseComponent(component);
+    LOG(LS_ERROR) << "Failed creating Apple Script description";
+    return false;
+  }
+
+  err = OSACompile(component, &script_desc, kOSAModeCanInteract, &script_id);
+  if (err != noErr) {
+    AEDisposeDesc(&script_desc);
+    if (script_id != kOSANullScript) {
+      OSADispose(component, script_id);
+    }
+    CloseComponent(component);
+    LOG(LS_ERROR) << "Error compiling Apple Script";
+    return false;
+  }
+
+  err = OSAExecute(component, script_id, kOSANullScript, kOSAModeCanInteract,
+		   &result_id);
+
+  if (err == errOSAScriptError) {
+    LOG(LS_ERROR) << "Error when executing Apple Script: " << script;
+    AECreateDesc(typeNull, NULL, 0, &result_data);
+    OSAScriptError(component, kOSAErrorMessage, typeChar, &result_data);
+    int len = AEGetDescDataSize(&result_data);
+    char* data = (char*) malloc(len);
+    if (data != NULL) {
+      err = AEGetDescData(&result_data, data, len);
+      LOG(LS_ERROR) << "Script error: " << data;
+    }
+    AEDisposeDesc(&script_desc);
+    AEDisposeDesc(&result_data);
+    return false;
+  }
+  AEDisposeDesc(&script_desc);
+  if (script_id != kOSANullScript) {
+    OSADispose(component, script_id);
+  }
+  if (result_id != kOSANullScript) {
+    OSADispose(component, result_id);
+  }
+  CloseComponent(component);
+  return true;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
diff --git a/talk/base/macutils.h b/talk/base/macutils.h
new file mode 100644
index 0000000..30bb300
--- /dev/null
+++ b/talk/base/macutils.h
@@ -0,0 +1,66 @@
+/*
+ * libjingle
+ * Copyright 2007--2009, 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_BASE_MACUTILS_H__
+#define TALK_BASE_MACUTILS_H__
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <Carbon/Carbon.h>
+#include <string>
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool ToUtf8(const CFStringRef str16, std::string* str8);
+bool ToUtf16(const std::string& str8, CFStringRef* str16);
+
+void DecodeFourChar(UInt32 fc, std::string* out);
+std::string DecodeEvent(EventRef event);
+
+enum MacOSVersionName {
+  kMacOSUnknown,  // ???
+  kMacOSOlder,    // 10.2-
+  kMacOSPanther,  // 10.3
+  kMacOSTiger,    // 10.4
+  kMacOSLeopard,  // 10.5
+  kMacOSNewer,    // 10.6+
+};
+
+bool GetOSVersion(int* major, int* minor, int* bugfix);
+MacOSVersionName GetOSVersionName();
+bool GetQuickTimeVersion(std::string* version);
+
+// Runs the given apple script. Only supports scripts that does not
+// require user interaction.
+bool RunAppleScript(const std::string& script);
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_MACUTILS_H__
diff --git a/talk/base/md5.h b/talk/base/md5.h
new file mode 100644
index 0000000..ec458d1
--- /dev/null
+++ b/talk/base/md5.h
@@ -0,0 +1,45 @@
+/*
+ * This is the header file for the MD5 message-digest algorithm.
+ * The algorithm is due to Ron Rivest.  This code was
+ * written by Colin Plumb in 1993, no copyright is claimed.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Equivalent code is available from RSA Data Security, Inc.
+ * This code has been tested against that, and is equivalent,
+ * except that you don't need to include two pages of legalese
+ * with every copy.
+ * To compute the message digest of a chunk of bytes, declare an
+ * MD5Context structure, pass it to MD5Init, call MD5Update as
+ * needed on buffers full of bytes, and then call MD5Final, which
+ * will fill a supplied 16-byte array with the digest.
+ *
+ */
+
+#ifndef TALK_BASE_MD5_H__
+#define TALK_BASE_MD5_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef long unsigned int uint32;
+typedef struct MD5Context MD5_CTX;
+
+#define md5byte unsigned char
+
+struct MD5Context {
+  uint32 buf[4];
+  uint32 bits[2];
+  uint32 in[16];
+};
+
+void MD5Init(struct MD5Context *context);
+void MD5Update(struct MD5Context *context, md5byte const *buf, unsigned len);
+void MD5Final(unsigned char digest[16], struct MD5Context *context);
+void MD5Transform(uint32 buf[4], uint32 const in[16]);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif // TALK_BASE_MD5_H__
diff --git a/talk/base/md5c.c b/talk/base/md5c.c
new file mode 100644
index 0000000..eb2c034
--- /dev/null
+++ b/talk/base/md5c.c
@@ -0,0 +1,256 @@
+/*
+ * This code implements the MD5 message-digest algorithm.
+ * The algorithm is due to Ron Rivest.  This code was
+ * written by Colin Plumb in 1993, no copyright is claimed.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Equivalent code is available from RSA Data Security, Inc.
+ * This code has been tested against that, and is equivalent,
+ * except that you don't need to include two pages of legalese
+ * with every copy.
+ *
+ * To compute the message digest of a chunk of bytes, declare an
+ * MD5Context structure, pass it to MD5Init, call MD5Update as
+ * needed on buffers full of bytes, and then call MD5Final, which
+ * will fill a supplied 16-byte array with the digest.
+ */
+#include <string.h>	/* for memcpy() */
+#include "md5.h"
+
+#ifndef HIGHFIRST
+#define byteReverse(buf, len)	/* Nothing */
+#else
+void byteReverse(unsigned char *buf, unsigned longs);
+
+#ifndef ASM_MD5
+/*
+ * Note: this code is harmless on little-endian machines.
+ */
+void byteReverse(unsigned char *buf, unsigned longs)
+{
+	uint32 t;
+	do {
+		t = (uint32)((unsigned)buf[3]<<8 | buf[2]) << 16 |
+		            ((unsigned)buf[1]<<8 | buf[0]);
+		*(uint32 *)buf = t;
+		buf += 4;
+	} while (--longs);
+}
+#endif
+#endif
+
+/*
+ * Start MD5 accumulation.  Set bit count to 0 and buffer to mysterious
+ * initialization constants.
+ */
+void
+MD5Init(struct MD5Context *ctx)
+{
+	ctx->buf[0] = 0x67452301;
+	ctx->buf[1] = 0xefcdab89;
+	ctx->buf[2] = 0x98badcfe;
+	ctx->buf[3] = 0x10325476;
+
+	ctx->bits[0] = 0;
+	ctx->bits[1] = 0;
+}
+
+/*
+ * Update context to reflect the concatenation of another buffer full
+ * of bytes.
+ */
+void
+MD5Update(struct MD5Context *ctx, unsigned char const *buf, unsigned len)
+{
+	uint32 t;
+
+	/* Update bitcount */
+
+	t = ctx->bits[0];
+	if ((ctx->bits[0] = t + ((uint32)len << 3)) < t)
+		ctx->bits[1]++;	/* Carry from low to high */
+	ctx->bits[1] += len >> 29;
+
+	t = (t >> 3) & 0x3f;	/* Bytes already in shsInfo->data */
+
+	/* Handle any leading odd-sized chunks */
+
+	if ( t ) {
+		unsigned char *p = (unsigned char *)ctx->in + t;
+
+		t = 64-t;
+		if (len < t) {
+			memcpy(p, buf, len);
+			return;
+		}
+		memcpy(p, buf, t);
+		byteReverse(ctx->in, 16);
+		MD5Transform(ctx->buf, (uint32 *)ctx->in);
+		buf += t;
+		len -= t;
+	}
+
+	/* Process data in 64-byte chunks */
+
+	while (len >= 64) {
+		memcpy(ctx->in, buf, 64);
+		byteReverse(ctx->in, 16);
+		MD5Transform(ctx->buf, (uint32 *)ctx->in);
+		buf += 64;
+		len -= 64;
+	}
+
+	/* Handle any remaining bytes of data. */
+
+	memcpy(ctx->in, buf, len);
+}
+
+/*
+ * Final wrapup - pad to 64-byte boundary with the bit pattern 
+ * 1 0* (64-bit count of bits processed, MSB-first)
+ */
+void
+MD5Final(unsigned char digest[16], struct MD5Context *ctx)
+{
+	unsigned count;
+	unsigned char *p;
+
+	/* Compute number of bytes mod 64 */
+	count = (ctx->bits[0] >> 3) & 0x3F;
+
+	/* Set the first char of padding to 0x80.  This is safe since there is
+	   always at least one byte free */
+	p = (unsigned char*)(ctx->in) + count;
+	*p++ = 0x80;
+
+	/* Bytes of padding needed to make 64 bytes */
+	count = 64 - 1 - count;
+
+	/* Pad out to 56 mod 64 */
+	if (count < 8) {
+		/* Two lots of padding:  Pad the first block to 64 bytes */
+		memset(p, 0, count);
+		byteReverse(ctx->in, 16);
+		MD5Transform(ctx->buf, (uint32 *)ctx->in);
+
+		/* Now fill the next block with 56 bytes */
+		memset(ctx->in, 0, 56);
+	} else {
+		/* Pad block to 56 bytes */
+		memset(p, 0, count-8);
+	}
+	byteReverse(ctx->in, 14);
+
+	/* Append length in bits and transform */
+	((uint32 *)ctx->in)[ 14 ] = ctx->bits[0];
+	((uint32 *)ctx->in)[ 15 ] = ctx->bits[1];
+
+	MD5Transform(ctx->buf, (uint32 *)ctx->in);
+	byteReverse((unsigned char *)ctx->buf, 4);
+	memcpy(digest, ctx->buf, 16);
+	memset(ctx, 0, sizeof(ctx));	/* In case it's sensitive */
+}
+
+#ifndef ASM_MD5
+
+/* The four core functions - F1 is optimized somewhat */
+
+/* #define F1(x, y, z) (x & y | ~x & z) */
+#define F1(x, y, z) (z ^ (x & (y ^ z)))
+#define F2(x, y, z) F1(z, x, y)
+#define F3(x, y, z) (x ^ y ^ z)
+#define F4(x, y, z) (y ^ (x | ~z))
+
+/* This is the central step in the MD5 algorithm. */
+#define MD5STEP(f, w, x, y, z, data, s) \
+	( w += f(x, y, z) + data,  w = w<<s | w>>(32-s),  w += x )
+
+/*
+ * The core of the MD5 algorithm, this alters an existing MD5 hash to
+ * reflect the addition of 16 longwords of new data.  MD5Update blocks
+ * the data and converts bytes into longwords for this routine.
+ */
+void
+MD5Transform(uint32 buf[4], uint32 const in[16])
+{
+	register uint32 a, b, c, d;
+
+	a = buf[0];
+	b = buf[1];
+	c = buf[2];
+	d = buf[3];
+
+	MD5STEP(F1, a, b, c, d, in[ 0]+0xd76aa478,  7);
+	MD5STEP(F1, d, a, b, c, in[ 1]+0xe8c7b756, 12);
+	MD5STEP(F1, c, d, a, b, in[ 2]+0x242070db, 17);
+	MD5STEP(F1, b, c, d, a, in[ 3]+0xc1bdceee, 22);
+	MD5STEP(F1, a, b, c, d, in[ 4]+0xf57c0faf,  7);
+	MD5STEP(F1, d, a, b, c, in[ 5]+0x4787c62a, 12);
+	MD5STEP(F1, c, d, a, b, in[ 6]+0xa8304613, 17);
+	MD5STEP(F1, b, c, d, a, in[ 7]+0xfd469501, 22);
+	MD5STEP(F1, a, b, c, d, in[ 8]+0x698098d8,  7);
+	MD5STEP(F1, d, a, b, c, in[ 9]+0x8b44f7af, 12);
+	MD5STEP(F1, c, d, a, b, in[10]+0xffff5bb1, 17);
+	MD5STEP(F1, b, c, d, a, in[11]+0x895cd7be, 22);
+	MD5STEP(F1, a, b, c, d, in[12]+0x6b901122,  7);
+	MD5STEP(F1, d, a, b, c, in[13]+0xfd987193, 12);
+	MD5STEP(F1, c, d, a, b, in[14]+0xa679438e, 17);
+	MD5STEP(F1, b, c, d, a, in[15]+0x49b40821, 22);
+
+	MD5STEP(F2, a, b, c, d, in[ 1]+0xf61e2562,  5);
+	MD5STEP(F2, d, a, b, c, in[ 6]+0xc040b340,  9);
+	MD5STEP(F2, c, d, a, b, in[11]+0x265e5a51, 14);
+	MD5STEP(F2, b, c, d, a, in[ 0]+0xe9b6c7aa, 20);
+	MD5STEP(F2, a, b, c, d, in[ 5]+0xd62f105d,  5);
+	MD5STEP(F2, d, a, b, c, in[10]+0x02441453,  9);
+	MD5STEP(F2, c, d, a, b, in[15]+0xd8a1e681, 14);
+	MD5STEP(F2, b, c, d, a, in[ 4]+0xe7d3fbc8, 20);
+	MD5STEP(F2, a, b, c, d, in[ 9]+0x21e1cde6,  5);
+	MD5STEP(F2, d, a, b, c, in[14]+0xc33707d6,  9);
+	MD5STEP(F2, c, d, a, b, in[ 3]+0xf4d50d87, 14);
+	MD5STEP(F2, b, c, d, a, in[ 8]+0x455a14ed, 20);
+	MD5STEP(F2, a, b, c, d, in[13]+0xa9e3e905,  5);
+	MD5STEP(F2, d, a, b, c, in[ 2]+0xfcefa3f8,  9);
+	MD5STEP(F2, c, d, a, b, in[ 7]+0x676f02d9, 14);
+	MD5STEP(F2, b, c, d, a, in[12]+0x8d2a4c8a, 20);
+
+	MD5STEP(F3, a, b, c, d, in[ 5]+0xfffa3942,  4);
+	MD5STEP(F3, d, a, b, c, in[ 8]+0x8771f681, 11);
+	MD5STEP(F3, c, d, a, b, in[11]+0x6d9d6122, 16);
+	MD5STEP(F3, b, c, d, a, in[14]+0xfde5380c, 23);
+	MD5STEP(F3, a, b, c, d, in[ 1]+0xa4beea44,  4);
+	MD5STEP(F3, d, a, b, c, in[ 4]+0x4bdecfa9, 11);
+	MD5STEP(F3, c, d, a, b, in[ 7]+0xf6bb4b60, 16);
+	MD5STEP(F3, b, c, d, a, in[10]+0xbebfbc70, 23);
+	MD5STEP(F3, a, b, c, d, in[13]+0x289b7ec6,  4);
+	MD5STEP(F3, d, a, b, c, in[ 0]+0xeaa127fa, 11);
+	MD5STEP(F3, c, d, a, b, in[ 3]+0xd4ef3085, 16);
+	MD5STEP(F3, b, c, d, a, in[ 6]+0x04881d05, 23);
+	MD5STEP(F3, a, b, c, d, in[ 9]+0xd9d4d039,  4);
+	MD5STEP(F3, d, a, b, c, in[12]+0xe6db99e5, 11);
+	MD5STEP(F3, c, d, a, b, in[15]+0x1fa27cf8, 16);
+	MD5STEP(F3, b, c, d, a, in[ 2]+0xc4ac5665, 23);
+
+	MD5STEP(F4, a, b, c, d, in[ 0]+0xf4292244,  6);
+	MD5STEP(F4, d, a, b, c, in[ 7]+0x432aff97, 10);
+	MD5STEP(F4, c, d, a, b, in[14]+0xab9423a7, 15);
+	MD5STEP(F4, b, c, d, a, in[ 5]+0xfc93a039, 21);
+	MD5STEP(F4, a, b, c, d, in[12]+0x655b59c3,  6);
+	MD5STEP(F4, d, a, b, c, in[ 3]+0x8f0ccc92, 10);
+	MD5STEP(F4, c, d, a, b, in[10]+0xffeff47d, 15);
+	MD5STEP(F4, b, c, d, a, in[ 1]+0x85845dd1, 21);
+	MD5STEP(F4, a, b, c, d, in[ 8]+0x6fa87e4f,  6);
+	MD5STEP(F4, d, a, b, c, in[15]+0xfe2ce6e0, 10);
+	MD5STEP(F4, c, d, a, b, in[ 6]+0xa3014314, 15);
+	MD5STEP(F4, b, c, d, a, in[13]+0x4e0811a1, 21);
+	MD5STEP(F4, a, b, c, d, in[ 4]+0xf7537e82,  6);
+	MD5STEP(F4, d, a, b, c, in[11]+0xbd3af235, 10);
+	MD5STEP(F4, c, d, a, b, in[ 2]+0x2ad7d2bb, 15);
+	MD5STEP(F4, b, c, d, a, in[ 9]+0xeb86d391, 21);
+
+	buf[0] += a;
+	buf[1] += b;
+	buf[2] += c;
+	buf[3] += d;
+}
+#endif
diff --git a/talk/base/messagehandler.cc b/talk/base/messagehandler.cc
new file mode 100644
index 0000000..5b3585b
--- /dev/null
+++ b/talk/base/messagehandler.cc
@@ -0,0 +1,37 @@
+/*
+ * 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/base/messagehandler.h"
+#include "talk/base/messagequeue.h"
+
+namespace talk_base {
+
+MessageHandler::~MessageHandler() {
+  MessageQueueManager::Instance()->Clear(this);
+}
+
+} // namespace talk_base
diff --git a/talk/base/messagehandler.h b/talk/base/messagehandler.h
new file mode 100644
index 0000000..eb7db9b
--- /dev/null
+++ b/talk/base/messagehandler.h
@@ -0,0 +1,46 @@
+/*
+ * 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_BASE_MESSAGEHANDLER_H__
+#define TALK_BASE_MESSAGEHANDLER_H__
+
+namespace talk_base {
+
+struct Message;
+
+// Messages get dispatched to a MessageHandler
+
+class MessageHandler {
+public:
+  virtual ~MessageHandler();
+
+  virtual void OnMessage(Message* msg) = 0;
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_MESSAGEHANDLER_H__
diff --git a/talk/base/messagequeue.cc b/talk/base/messagequeue.cc
new file mode 100644
index 0000000..8c3a8ce
--- /dev/null
+++ b/talk/base/messagequeue.cc
@@ -0,0 +1,383 @@
+/*
+ * 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.
+ */
+
+#if defined(_MSC_VER) && _MSC_VER < 1300
+#pragma warning(disable:4786)
+#endif
+
+#ifdef POSIX
+#include <sys/time.h>
+#endif
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/messagequeue.h"
+#include "talk/base/physicalsocketserver.h"
+
+
+namespace talk_base {
+
+const uint32 kMaxMsgLatency = 150;  // 150 ms
+
+//------------------------------------------------------------------
+// MessageQueueManager
+
+MessageQueueManager* MessageQueueManager::instance_;
+
+MessageQueueManager* MessageQueueManager::Instance() {
+  // Note: This is not thread safe, but it is first called before threads are
+  // spawned.
+  if (!instance_)
+    instance_ = new MessageQueueManager;
+  return instance_;
+}
+
+MessageQueueManager::MessageQueueManager() {
+}
+
+MessageQueueManager::~MessageQueueManager() {
+}
+
+void MessageQueueManager::Add(MessageQueue *message_queue) {
+  // MessageQueueManager methods should be non-reentrant, so we
+  // ASSERT that is the case.  If any of these ASSERT, please
+  // contact bpm or jbeda.
+  ASSERT(!crit_.CurrentThreadIsOwner());
+  CritScope cs(&crit_);
+  message_queues_.push_back(message_queue);
+}
+
+void MessageQueueManager::Remove(MessageQueue *message_queue) {
+  ASSERT(!crit_.CurrentThreadIsOwner());  // See note above.
+  // If this is the last MessageQueue, destroy the manager as well so that
+  // we don't leak this object at program shutdown. As mentioned above, this is
+  // not thread-safe, but this should only happen at program termination (when
+  // the ThreadManager is destroyed, and threads are no longer active).
+  bool destroy = false;
+  {
+    CritScope cs(&crit_);
+    std::vector<MessageQueue *>::iterator iter;
+    iter = std::find(message_queues_.begin(), message_queues_.end(),
+                     message_queue);
+    if (iter != message_queues_.end()) {
+      message_queues_.erase(iter);      
+    }
+    destroy = message_queues_.empty();
+  }
+  if (destroy) {
+    instance_ = NULL;
+    delete this;    
+  }
+}
+
+void MessageQueueManager::Clear(MessageHandler *handler) {
+  ASSERT(!crit_.CurrentThreadIsOwner());  // See note above.
+  CritScope cs(&crit_);
+  std::vector<MessageQueue *>::iterator iter;
+  for (iter = message_queues_.begin(); iter != message_queues_.end(); iter++)
+    (*iter)->Clear(handler);
+}
+
+//------------------------------------------------------------------
+// MessageQueue
+
+MessageQueue::MessageQueue(SocketServer* ss)
+    : ss_(ss), fStop_(false), fPeekKeep_(false), active_(false),
+      dmsgq_next_num_(0) {
+  if (!ss_) {
+    // Currently, MessageQueue holds a socket server, and is the base class for
+    // Thread.  It seems like it makes more sense for Thread to hold the socket
+    // server, and provide it to the MessageQueue, since the Thread controls
+    // the I/O model, and MQ is agnostic to those details.  Anyway, this causes
+    // messagequeue_unittest to depend on network libraries... yuck.
+    default_ss_.reset(new PhysicalSocketServer());
+    ss_ = default_ss_.get();
+  }
+  ss_->SetMessageQueue(this);
+}
+
+MessageQueue::~MessageQueue() {
+  // The signal is done from here to ensure
+  // that it always gets called when the queue
+  // is going away.
+  SignalQueueDestroyed();
+  if (active_) {
+    MessageQueueManager::Instance()->Remove(this);
+    Clear(NULL);
+  }
+  if (ss_) {
+    ss_->SetMessageQueue(NULL);
+  }
+}
+
+void MessageQueue::set_socketserver(SocketServer* ss) {
+  ss_ = ss ? ss : default_ss_.get();
+  ss_->SetMessageQueue(this);
+}
+
+void MessageQueue::Quit() {
+  fStop_ = true;
+  ss_->WakeUp();
+}
+
+bool MessageQueue::IsQuitting() {
+  return fStop_;
+}
+
+void MessageQueue::Restart() {
+  fStop_ = false;
+}
+
+bool MessageQueue::Peek(Message *pmsg, int cmsWait) {
+  if (fPeekKeep_) {
+    *pmsg = msgPeek_;
+    return true;
+  }
+  if (!Get(pmsg, cmsWait))
+    return false;
+  msgPeek_ = *pmsg;
+  fPeekKeep_ = true;
+  return true;
+}
+
+bool MessageQueue::Get(Message *pmsg, int cmsWait, bool process_io) {
+  // Return and clear peek if present
+  // Always return the peek if it exists so there is Peek/Get symmetry
+
+  if (fPeekKeep_) {
+    *pmsg = msgPeek_;
+    fPeekKeep_ = false;
+    return true;
+  }
+
+  // Get w/wait + timer scan / dispatch + socket / event multiplexer dispatch
+
+  int cmsTotal = cmsWait;
+  int cmsElapsed = 0;
+  uint32 msStart = Time();
+  uint32 msCurrent = msStart;
+  while (true) {
+    // Check for sent messages
+
+    ReceiveSends();
+
+    // Check queues
+
+    int cmsDelayNext = kForever;
+    {
+      CritScope cs(&crit_);
+
+      // Check for delayed messages that have been triggered
+      // Calc the next trigger too
+
+      while (!dmsgq_.empty()) {
+        if (TimeIsLater(msCurrent, dmsgq_.top().msTrigger_)) {
+          cmsDelayNext = TimeDiff(dmsgq_.top().msTrigger_, msCurrent);
+          break;
+        }
+        msgq_.push_back(dmsgq_.top().msg_);
+        dmsgq_.pop();
+      }
+
+      // Check for posted events
+
+      while (!msgq_.empty()) {
+        *pmsg = msgq_.front();
+        if (pmsg->ts_sensitive) {
+          long delay = TimeDiff(msCurrent, pmsg->ts_sensitive);
+          if (delay > 0) {
+            LOG_F(LS_WARNING) << "id: " << pmsg->message_id << "  delay: "
+                              << (delay + kMaxMsgLatency) << "ms";
+          }
+        }
+        msgq_.pop_front();
+        if (MQID_DISPOSE == pmsg->message_id) {
+          ASSERT(NULL == pmsg->phandler);
+          delete pmsg->pdata;
+          continue;
+        }
+        return true;
+      }
+    }
+
+    if (fStop_)
+      break;
+
+    // Which is shorter, the delay wait or the asked wait?
+
+    int cmsNext;
+    if (cmsWait == kForever) {
+      cmsNext = cmsDelayNext;
+    } else {
+      cmsNext = _max(0, cmsTotal - cmsElapsed);
+      if ((cmsDelayNext != kForever) && (cmsDelayNext < cmsNext))
+        cmsNext = cmsDelayNext;
+    }
+
+    // Wait and multiplex in the meantime
+    if (!ss_->Wait(cmsNext, process_io))
+      return false;
+
+    // If the specified timeout expired, return
+
+    msCurrent = Time();
+    cmsElapsed = TimeDiff(msCurrent, msStart);
+    if (cmsWait != kForever) {
+      if (cmsElapsed >= cmsWait)
+        return false;
+    }
+  }
+  return false;
+}
+
+void MessageQueue::ReceiveSends() {
+}
+
+void MessageQueue::Post(MessageHandler *phandler, uint32 id,
+    MessageData *pdata, bool time_sensitive) {
+  if (fStop_)
+    return;
+
+  // Keep thread safe
+  // Add the message to the end of the queue
+  // Signal for the multiplexer to return
+
+  CritScope cs(&crit_);
+  EnsureActive();
+  Message msg;
+  msg.phandler = phandler;
+  msg.message_id = id;
+  msg.pdata = pdata;
+  if (time_sensitive) {
+    msg.ts_sensitive = Time() + kMaxMsgLatency;
+  }
+  msgq_.push_back(msg);
+  ss_->WakeUp();
+}
+
+void MessageQueue::DoDelayPost(int cmsDelay, uint32 tstamp,
+    MessageHandler *phandler, uint32 id, MessageData* pdata) {
+  if (fStop_)
+    return;
+
+  // Keep thread safe
+  // Add to the priority queue. Gets sorted soonest first.
+  // Signal for the multiplexer to return.
+
+  CritScope cs(&crit_);
+  EnsureActive();
+  Message msg;
+  msg.phandler = phandler;
+  msg.message_id = id;
+  msg.pdata = pdata;
+  DelayedMessage dmsg(cmsDelay, tstamp, dmsgq_next_num_, msg);
+  dmsgq_.push(dmsg);
+  // If this message queue processes 1 message every millisecond for 50 days,
+  // we will wrap this number.  Even then, only messages with identical times
+  // will be misordered, and then only briefly.  This is probably ok.
+  VERIFY(0 != ++dmsgq_next_num_);
+  ss_->WakeUp();
+}
+
+int MessageQueue::GetDelay() {
+  CritScope cs(&crit_);
+
+  if (!msgq_.empty())
+    return 0;
+
+  if (!dmsgq_.empty()) {
+    int delay = TimeUntil(dmsgq_.top().msTrigger_);
+    if (delay < 0)
+      delay = 0;
+    return delay;
+  }
+
+  return kForever;
+}
+
+void MessageQueue::Clear(MessageHandler *phandler, uint32 id,
+                         MessageList* removed) {
+  CritScope cs(&crit_);
+
+  // Remove messages with phandler
+
+  if (fPeekKeep_ && msgPeek_.Match(phandler, id)) {
+    if (removed) {
+      removed->push_back(msgPeek_);
+    } else {
+      delete msgPeek_.pdata;
+    }
+    fPeekKeep_ = false;
+  }
+
+  // Remove from ordered message queue
+
+  for (MessageList::iterator it = msgq_.begin(); it != msgq_.end();) {
+    if (it->Match(phandler, id)) {
+      if (removed) {
+        removed->push_back(*it);
+      } else {
+        delete it->pdata;
+      }
+      it = msgq_.erase(it);
+    } else {
+      ++it;
+    }
+  }
+
+  // Remove from priority queue. Not directly iterable, so use this approach
+
+  PriorityQueue::container_type::iterator new_end = dmsgq_.container().begin();
+  for (PriorityQueue::container_type::iterator it = new_end;
+       it != dmsgq_.container().end(); ++it) {
+    if (it->msg_.Match(phandler, id)) {
+      if (removed) {
+        removed->push_back(it->msg_);
+      } else {
+        delete it->msg_.pdata;
+      }
+    } else {
+      *new_end++ = *it;
+    }
+  }
+  dmsgq_.container().erase(new_end, dmsgq_.container().end());
+  dmsgq_.reheap();
+}
+
+void MessageQueue::Dispatch(Message *pmsg) {
+  pmsg->phandler->OnMessage(pmsg);
+}
+
+void MessageQueue::EnsureActive() {
+  ASSERT(crit_.CurrentThreadIsOwner());
+  if (!active_) {
+    active_ = true;
+    MessageQueueManager::Instance()->Add(this);
+  }
+}
+
+}  // namespace talk_base
diff --git a/talk/base/messagequeue.h b/talk/base/messagequeue.h
new file mode 100644
index 0000000..4d470df
--- /dev/null
+++ b/talk/base/messagequeue.h
@@ -0,0 +1,245 @@
+/*
+ * 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_BASE_MESSAGEQUEUE_H_
+#define TALK_BASE_MESSAGEQUEUE_H_
+
+#include <algorithm>
+#include <cstring>
+#include <list>
+#include <queue>
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/criticalsection.h"
+#include "talk/base/messagehandler.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/socketserver.h"
+#include "talk/base/time.h"
+
+namespace talk_base {
+
+struct Message;
+class MessageQueue;
+
+// MessageQueueManager does cleanup of of message queues
+
+class MessageQueueManager {
+ public:
+  static MessageQueueManager* Instance();
+
+  void Add(MessageQueue *message_queue);
+  void Remove(MessageQueue *message_queue);
+  void Clear(MessageHandler *handler);
+
+ private:
+  MessageQueueManager();
+  ~MessageQueueManager();
+
+  static MessageQueueManager* instance_;
+  // This list contains 'active' MessageQueues.
+  std::vector<MessageQueue *> message_queues_;
+  CriticalSection crit_;
+};
+
+// Derive from this for specialized data
+// App manages lifetime, except when messages are purged
+
+class MessageData {
+ public:
+  MessageData() {}
+  virtual ~MessageData() {}
+};
+
+template <class T>
+class TypedMessageData : public MessageData {
+ public:
+  explicit TypedMessageData(const T& data) : data_(data) { }
+  const T& data() const { return data_; }
+  T& data() { return data_; }
+ private:
+  T data_;
+};
+
+// Like TypedMessageData, but for pointers that require a delete.
+template <class T>
+class ScopedMessageData : public MessageData {
+ public:
+  explicit ScopedMessageData(T* data) : data_(data) { }
+  const scoped_ptr<T>& data() const { return data_; }
+  scoped_ptr<T>& data() { return data_; }
+ private:
+  scoped_ptr<T> data_;
+};
+
+template<class T>
+inline MessageData* WrapMessageData(const T& data) {
+  return new TypedMessageData<T>(data);
+}
+
+template<class T>
+inline const T& UseMessageData(MessageData* data) {
+  return static_cast< TypedMessageData<T>* >(data)->data();
+}
+
+template<class T>
+class DisposeData : public MessageData {
+ public:
+  explicit DisposeData(T* data) : data_(data) { }
+  virtual ~DisposeData() { delete data_; }
+ private:
+  T* data_;
+};
+
+const uint32 MQID_ANY = static_cast<uint32>(-1);
+const uint32 MQID_DISPOSE = static_cast<uint32>(-2);
+
+// No destructor
+
+struct Message {
+  Message() {
+    memset(this, 0, sizeof(*this));
+  }
+  inline bool Match(MessageHandler* handler, uint32 id) const {
+    return (handler == NULL || handler == phandler)
+           && (id == MQID_ANY || id == message_id);
+  }
+  MessageHandler *phandler;
+  uint32 message_id;
+  MessageData *pdata;
+  uint32 ts_sensitive;
+};
+
+typedef std::list<Message> MessageList;
+
+// DelayedMessage goes into a priority queue, sorted by trigger time.  Messages
+// with the same trigger time are processed in num_ (FIFO) order.
+
+class DelayedMessage {
+ public:
+  DelayedMessage(int delay, uint32 trigger, uint32 num, const Message& msg)
+  : cmsDelay_(delay), msTrigger_(trigger), num_(num), msg_(msg) { }
+
+  bool operator< (const DelayedMessage& dmsg) const {
+    return (dmsg.msTrigger_ < msTrigger_)
+           || ((dmsg.msTrigger_ == msTrigger_) && (dmsg.num_ < num_));
+  }
+
+  int cmsDelay_;  // for debugging
+  uint32 msTrigger_;
+  uint32 num_;
+  Message msg_;
+};
+
+class MessageQueue {
+ public:
+  explicit MessageQueue(SocketServer* ss = NULL);
+  virtual ~MessageQueue();
+
+  SocketServer* socketserver() { return ss_; }
+  void set_socketserver(SocketServer* ss);
+
+  // Note: The behavior of MessageQueue has changed.  When a MQ is stopped,
+  // futher Posts and Sends will fail.  However, any pending Sends and *ready*
+  // Posts (as opposed to unexpired delayed Posts) will be delivered before
+  // Get (or Peek) returns false.  By guaranteeing delivery of those messages,
+  // we eliminate the race condition when an MessageHandler and MessageQueue
+  // may be destroyed independently of each other.
+  virtual void Quit();
+  virtual bool IsQuitting();
+  virtual void Restart();
+
+  // Get() will process I/O until:
+  //  1) A message is available (returns true)
+  //  2) cmsWait seconds have elapsed (returns false)
+  //  3) Stop() is called (returns false)
+  virtual bool Get(Message *pmsg, int cmsWait = kForever,
+                   bool process_io = true);
+  virtual bool Peek(Message *pmsg, int cmsWait = 0);
+  virtual void Post(MessageHandler *phandler, uint32 id = 0,
+                    MessageData *pdata = NULL, bool time_sensitive = false);
+  virtual void PostDelayed(int cmsDelay, MessageHandler *phandler,
+                           uint32 id = 0, MessageData *pdata = NULL) {
+    return DoDelayPost(cmsDelay, TimeAfter(cmsDelay), phandler, id, pdata);
+  }
+  virtual void PostAt(uint32 tstamp, MessageHandler *phandler,
+                      uint32 id = 0, MessageData *pdata = NULL) {
+    return DoDelayPost(TimeUntil(tstamp), tstamp, phandler, id, pdata);
+  }
+  virtual void Clear(MessageHandler *phandler, uint32 id = MQID_ANY,
+                     MessageList* removed = NULL);
+  virtual void Dispatch(Message *pmsg);
+  virtual void ReceiveSends();
+
+  // Amount of time until the next message can be retrieved
+  virtual int GetDelay();
+
+  bool empty() const { return msgq_.empty() && dmsgq_.empty() && !fPeekKeep_; }
+  size_t size() const { return msgq_.size() + dmsgq_.size() + fPeekKeep_; }
+
+  // Internally posts a message which causes the doomed object to be deleted
+  template<class T> void Dispose(T* doomed) {
+    if (doomed) {
+      Post(NULL, MQID_DISPOSE, new DisposeData<T>(doomed));
+    }
+  }
+
+  // When this signal is sent out, any references to this queue should
+  // no longer be used.
+  sigslot::signal0<> SignalQueueDestroyed;
+
+ protected:
+  class PriorityQueue : public std::priority_queue<DelayedMessage> {
+   public:
+    container_type& container() { return c; }
+    void reheap() { make_heap(c.begin(), c.end(), comp); }
+  };
+
+  void EnsureActive();
+  void DoDelayPost(int cmsDelay, uint32 tstamp, MessageHandler *phandler,
+                   uint32 id, MessageData* pdata);
+
+  // The SocketServer is not owned by MessageQueue.
+  SocketServer* ss_;
+  // If a server isn't supplied in the constructor, use this one.
+  scoped_ptr<SocketServer> default_ss_;
+  bool fStop_;
+  bool fPeekKeep_;
+  Message msgPeek_;
+  // A message queue is active if it has ever had a message posted to it.
+  // This also corresponds to being in MessageQueueManager's global list.
+  bool active_;
+  MessageList msgq_;
+  PriorityQueue dmsgq_;
+  uint32 dmsgq_next_num_;
+  CriticalSection crit_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_MESSAGEQUEUE_H_
diff --git a/talk/base/nethelpers.cc b/talk/base/nethelpers.cc
new file mode 100644
index 0000000..b877d78
--- /dev/null
+++ b/talk/base/nethelpers.cc
@@ -0,0 +1,175 @@
+/*
+ * 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.
+ */
+
+#include "talk/base/nethelpers.h"
+
+#include "talk/base/byteorder.h"
+#include "talk/base/signalthread.h"
+
+namespace talk_base {
+
+#if defined(LINUX) || defined(ANDROID)
+static const size_t kInitHostentLen = 1024;
+static const size_t kMaxHostentLen = kInitHostentLen * 8;
+#endif
+
+// AsyncResolver
+
+AsyncResolver::AsyncResolver() : result_(NULL), error_(0) {
+}
+
+AsyncResolver::~AsyncResolver() {
+  FreeHostEnt(result_);
+}
+
+void AsyncResolver::DoWork() {
+  result_ = SafeGetHostByName(addr_.hostname().c_str(), &error_);
+}
+
+void AsyncResolver::OnWorkDone() {
+  if (result_) {
+    addr_.SetIP(NetworkToHost32(
+        *reinterpret_cast<uint32*>(result_->h_addr_list[0])));
+  }
+}
+
+// The functions below are used to do gethostbyname, but with an allocated
+// instead of a static buffer.
+hostent* SafeGetHostByName(const char* hostname, int* herrno) {
+  if (NULL == hostname || NULL == herrno) {
+    return NULL;
+  }
+  hostent* result = NULL;
+#if defined(WIN32)
+  // On Windows we have to allocate a buffer, and manually copy the hostent,
+  // along with its embedded pointers.
+  hostent* ent = gethostbyname(hostname);
+  if (!ent) {
+    *herrno = WSAGetLastError();
+    return NULL;
+  }
+
+  // Get the total number of bytes we need to copy, and allocate our buffer.
+  int num_aliases = 0, num_addrs = 0;
+  int total_len = sizeof(hostent);
+  total_len += strlen(ent->h_name) + 1;
+  while (ent->h_aliases[num_aliases]) {
+    total_len += sizeof(char*) + strlen(ent->h_aliases[num_aliases]) + 1;
+    ++num_aliases;
+  }
+  total_len += sizeof(char*);
+  while (ent->h_addr_list[num_addrs]) {
+    total_len += sizeof(char*) + ent->h_length;
+    ++num_addrs;
+  }
+  total_len += sizeof(char*);
+
+  result = static_cast<hostent*>(malloc(total_len));
+  if (NULL == result) {
+    return NULL;
+  }
+  char* p = reinterpret_cast<char*>(result) + sizeof(hostent);
+
+  // Copy the hostent into it, along with its embedded pointers.
+  result->h_name = p;
+  memcpy(p, ent->h_name, strlen(ent->h_name) + 1);
+  p += strlen(ent->h_name) + 1;
+
+  result->h_aliases = reinterpret_cast<char**>(p);
+  p += (num_aliases + 1) * sizeof(char*);
+  for (int i = 0; i < num_aliases; ++i) {
+    result->h_aliases[i] = p;
+    memcpy(p, ent->h_aliases[i], strlen(ent->h_aliases[i]) + 1);
+    p += strlen(ent->h_aliases[i]) + 1;
+  }
+  result->h_aliases[num_aliases] = NULL;
+
+  result->h_addrtype = ent->h_addrtype;
+  result->h_length = ent->h_length;
+
+  result->h_addr_list = reinterpret_cast<char**>(p);
+  p += (num_addrs + 1) * sizeof(char*);
+  for (int i = 0; i < num_addrs; ++i) {
+    result->h_addr_list[i] = p;
+    memcpy(p, ent->h_addr_list[i], ent->h_length);
+    p += ent->h_length;
+  }
+  result->h_addr_list[num_addrs] = NULL;
+
+  *herrno = 0;
+#elif defined(LINUX) || defined(ANDROID)
+  // gethostbyname() is not thread safe, so we need to call gethostbyname_r()
+  // which is a reentrant version of gethostbyname().
+  ASSERT(kInitHostentLen > sizeof(hostent));
+  size_t size = kInitHostentLen;
+  int ret;
+  void* buf = malloc(size);
+  if (NULL == buf) {
+    return NULL;
+  }
+  char* aux = static_cast<char*>(buf) + sizeof(hostent);
+  size_t aux_len = size - sizeof(hostent);
+  while ((ret = gethostbyname_r(hostname, reinterpret_cast<hostent*>(buf), aux,
+      aux_len, &result, herrno)) == ERANGE) {
+    size *= 2;
+    if (size > kMaxHostentLen) {
+      break;  // Just to be safe.
+    }
+    buf = realloc(buf, size);
+    if (NULL == buf) {
+      return NULL;
+    }
+    aux = static_cast<char*>(buf) + sizeof(hostent);
+    aux_len = size - sizeof(hostent);
+  }
+  if (ret != 0 || buf != result) {
+    free(buf);
+    return NULL;
+  }
+  *herrno = 0;
+#elif defined(OSX) || defined(IOS)
+  // Mac OS returns an object with everything allocated.
+  result = getipnodebyname(hostname, AF_INET, AI_DEFAULT, herrno);
+#else
+#error "I don't know how to do gethostbyname safely on your system."
+#endif
+  return result;
+}
+
+// This function should mirror the above function, and free any resources
+// allocated by the above.
+void FreeHostEnt(hostent* host) {
+#if defined(OSX) || defined(IOS)
+  freehostent(host);
+#elif defined(WIN32) || defined(POSIX)
+  free(host);
+#else
+#error "I don't know how to free a hostent on your system."
+#endif
+}
+
+}  // namespace talk_base
diff --git a/talk/base/nethelpers.h b/talk/base/nethelpers.h
new file mode 100644
index 0000000..4cba9a9
--- /dev/null
+++ b/talk/base/nethelpers.h
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ */
+
+#ifndef TALK_BASE_NETHELPERS_H_
+#define TALK_BASE_NETHELPERS_H_
+
+#ifdef POSIX
+#include <netdb.h>
+#include <cstddef>
+#elif WIN32
+#include <winsock2.h>  // NOLINT
+#endif
+
+#include <list>
+
+#include "talk/base/signalthread.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/socketaddress.h"
+
+namespace talk_base {
+
+// AsyncResolver will perform async DNS resolution, signaling the result on
+// the inherited SignalWorkDone when the operation completes.
+class AsyncResolver : public SignalThread {
+ public:
+  AsyncResolver();
+
+  const SocketAddress& address() const { return addr_; }
+  void set_address(const SocketAddress& addr) { addr_ = addr; }
+  int error() const { return error_; }
+  void set_error(int error) { error_ = error; }
+
+ protected:
+  ~AsyncResolver();
+  virtual void DoWork();
+  virtual void OnWorkDone();
+
+ private:
+  SocketAddress addr_;
+  hostent* result_;
+  int error_;
+};
+
+// SafeGetHostByName functions allocate and return their result, instead of
+// using a static variable like the normal gethostbyname.
+// FreeHostEnt frees the memory allocated by SafeGetHostByName.
+hostent* SafeGetHostByName(const char* hostname, int* herrno);
+void FreeHostEnt(hostent* host);
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_NETHELPERS_H_
diff --git a/talk/base/network.cc b/talk/base/network.cc
new file mode 100644
index 0000000..8a56d0a
--- /dev/null
+++ b/talk/base/network.cc
@@ -0,0 +1,456 @@
+/*
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "talk/base/network.h"
+#include "talk/base/stream.h"
+
+#ifdef POSIX
+#include <sys/socket.h>
+#include <sys/utsname.h>
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <unistd.h>
+#include <errno.h>
+#endif  // POSIX
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#include <Iphlpapi.h>
+#endif
+
+#include <algorithm>
+#include <cassert>
+#include <cfloat>
+#include <cmath>
+#include <cstdio>
+#include <cstring>
+#include <sstream>
+
+#include "talk/base/host.h"
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/socket.h"  // includes something that makes windows happy
+#include "talk/base/stringencode.h"
+#include "talk/base/time.h"
+
+namespace {
+
+const double kAlpha = 0.5;  // weight for data infinitely far in the past
+const double kHalfLife = 2000;  // half life of exponential decay (in ms)
+const double kLog2 = 0.693147180559945309417;
+const double kLambda = kLog2 / kHalfLife;
+
+// assume so-so quality unless data says otherwise
+const double kDefaultQuality = talk_base::QUALITY_FAIR;
+
+typedef std::map<std::string, std::string> StrMap;
+
+void BuildMap(const StrMap& map, std::string& str) {
+  str.append("{");
+  bool first = true;
+  for (StrMap::const_iterator i = map.begin(); i != map.end(); ++i) {
+    if (!first) str.append(",");
+    str.append(i->first);
+    str.append("=");
+    str.append(i->second);
+    first = false;
+  }
+  str.append("}");
+}
+
+void ParseCheck(std::istringstream& ist, char ch) {
+  if (ist.get() != ch)
+    LOG(LERROR) << "Expecting '" << ch << "'";
+}
+
+std::string ParseString(std::istringstream& ist) {
+  std::string str;
+  int count = 0;
+  while (ist) {
+    char ch = ist.peek();
+    if ((count == 0) && ((ch == '=') || (ch == ',') || (ch == '}'))) {
+      break;
+    } else if (ch == '{') {
+      count += 1;
+    } else if (ch == '}') {
+      count -= 1;
+      if (count < 0)
+        LOG(LERROR) << "mismatched '{' and '}'";
+    }
+    str.append(1, static_cast<char>(ist.get()));
+  }
+  return str;
+}
+
+void ParseMap(const std::string& str, StrMap& map) {
+  if (str.size() == 0)
+    return;
+  std::istringstream ist(str);
+  ParseCheck(ist, '{');
+  for (;;) {
+    std::string key = ParseString(ist);
+    ParseCheck(ist, '=');
+    std::string val = ParseString(ist);
+    map[key] = val;
+    if (ist.peek() == ',')
+      ist.get();
+    else
+      break;
+  }
+  ParseCheck(ist, '}');
+  if (ist.rdbuf()->in_avail() != 0)
+    LOG(LERROR) << "Unexpected characters at end";
+}
+
+}  // namespace
+
+namespace talk_base {
+
+NetworkManager::~NetworkManager() {
+  for (NetworkMap::iterator i = networks_.begin(); i != networks_.end(); ++i)
+    delete i->second;
+}
+
+bool NetworkManager::GetNetworks(std::vector<Network*>* result) {
+  std::vector<Network*> list;
+  if (!EnumNetworks(false, &list)) {
+    return false;
+  }
+
+  for (uint32 i = 0; i < list.size(); ++i) {
+    NetworkMap::iterator iter = networks_.find(list[i]->name());
+
+    Network* network;
+    if (iter == networks_.end()) {
+      network = list[i];
+    } else {
+      network = iter->second;
+      network->set_ip(list[i]->ip());
+      network->set_gateway_ip(list[i]->gateway_ip());
+      delete list[i];
+    }
+
+    networks_[network->name()] = network;
+    result->push_back(network);
+  }
+  return true;
+}
+
+void NetworkManager::DumpNetworks(bool include_ignored) {
+  std::vector<Network*> list;
+  EnumNetworks(include_ignored, &list);
+  LOG(LS_INFO) << "NetworkManager detected " << list.size() << " networks:";
+  for (size_t i = 0; i < list.size(); ++i) {
+    const Network* network = list[i];
+    if (!network->ignored() || include_ignored) {
+      LOG(LS_INFO) << network->ToString() << ": " << network->description()
+                   << ", Gateway="
+                   << SocketAddress::IPToString(network->gateway_ip())
+                   << ((network->ignored()) ? ", Ignored" : "");
+    }
+  }
+}
+
+std::string NetworkManager::GetState() const {
+  StrMap map;
+  for (NetworkMap::const_iterator i = networks_.begin();
+       i != networks_.end(); ++i)
+    map[i->first] = i->second->GetState();
+
+  std::string str;
+  BuildMap(map, str);
+  return str;
+}
+
+void NetworkManager::SetState(const std::string& str) {
+  StrMap map;
+  ParseMap(str, map);
+
+  for (StrMap::iterator i = map.begin(); i != map.end(); ++i) {
+    std::string name = i->first;
+    std::string state = i->second;
+
+    Network* network = new Network(name, "", 0, 0);
+    network->SetState(state);
+    networks_[name] = network;
+  }
+}
+
+#ifdef POSIX
+// Gets the default gateway for the specified interface.
+uint32 GetDefaultGateway(const std::string& name) {
+#ifdef OSX
+  // TODO: /proc/net/route doesn't exist, 
+  // Use ioctl to get the routing table
+  return 0xFFFFFFFF;
+#endif
+
+  uint32 gateway_ip = 0;
+
+  FileStream fs;
+  if (fs.Open("/proc/net/route", "r")) {
+    std::string line;
+    while (fs.ReadLine(&line) == SR_SUCCESS && gateway_ip == 0) {
+      char iface[16];
+      unsigned int ip, gw;
+      if (sscanf(line.c_str(), "%7s %8X %8X", iface, &ip, &gw) == 3 &&
+          name == iface && ip == 0) {
+        gateway_ip = ntohl(gw);
+      }
+    }
+  }
+
+  return gateway_ip;
+}
+
+
+bool NetworkManager::CreateNetworks(bool include_ignored,
+                                    std::vector<Network*>* networks) {
+  int fd;
+  if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
+    LOG_ERR(LERROR) << "socket";
+    return false;
+  }
+
+  struct ifconf ifc;
+  ifc.ifc_len = 64 * sizeof(struct ifreq);
+  ifc.ifc_buf = new char[ifc.ifc_len];
+
+  if (ioctl(fd, SIOCGIFCONF, &ifc) < 0) {
+    LOG_ERR(LERROR) << "ioctl";
+    return false;
+  }
+  assert(ifc.ifc_len < static_cast<int>(64 * sizeof(struct ifreq)));
+
+  struct ifreq* ptr = reinterpret_cast<struct ifreq*>(ifc.ifc_buf);
+  struct ifreq* end =
+      reinterpret_cast<struct ifreq*>(ifc.ifc_buf + ifc.ifc_len);
+
+  while (ptr < end) {
+    struct sockaddr_in* inaddr =
+        reinterpret_cast<struct sockaddr_in*>(&ptr->ifr_ifru.ifru_addr);
+    if (inaddr->sin_family == AF_INET) {
+      uint32 ip = ntohl(inaddr->sin_addr.s_addr);
+      scoped_ptr<Network> network(
+          new Network(ptr->ifr_name, ptr->ifr_name, ip,
+                      GetDefaultGateway(ptr->ifr_name)));
+      network->set_ignored(IsIgnoredNetwork(*network));
+      if (include_ignored || !network->ignored()) {
+        networks->push_back(network.release());
+      }
+    }
+
+#ifdef _SIZEOF_ADDR_IFREQ
+    ptr = reinterpret_cast<struct ifreq*>(
+        reinterpret_cast<char*>(ptr) + _SIZEOF_ADDR_IFREQ(*ptr));
+#else
+    ptr++;
+#endif
+  }
+
+  delete [] ifc.ifc_buf;
+  close(fd);
+  return true;
+}
+#endif  // POSIX
+
+#ifdef WIN32
+bool NetworkManager::CreateNetworks(bool include_ignored,
+                                    std::vector<Network*>* networks) {
+  IP_ADAPTER_INFO info_temp;
+  ULONG len = 0;
+
+  if (GetAdaptersInfo(&info_temp, &len) != ERROR_BUFFER_OVERFLOW)
+    // This just means there's zero networks, which is not an error.
+    return true;
+
+  scoped_array<char> buf(new char[len]);
+  IP_ADAPTER_INFO *infos = reinterpret_cast<IP_ADAPTER_INFO *>(buf.get());
+  DWORD ret = GetAdaptersInfo(infos, &len);
+  if (ret != NO_ERROR) {
+    LOG_ERR_EX(LS_ERROR, ret) << "GetAdaptersInfo failed";
+    return false;
+  }
+
+  int count = 0;
+  for (IP_ADAPTER_INFO *info = infos; info != NULL; info = info->Next) {
+    // Ignore the loopback device.
+    if (info->Type == MIB_IF_TYPE_LOOPBACK) {
+      continue;
+    }
+
+    // In non-debug builds, don't transmit the network name because of
+    // privacy concerns. Transmit a number instead.
+    std::string name;
+#ifdef _DEBUG
+    name = info->Description;
+#else  // !_DEBUG
+    std::ostringstream ost;
+    ost << count;
+    name = ost.str();
+    count++;
+#endif  // !_DEBUG
+
+    scoped_ptr<Network> network(new Network(name, info->Description,
+        SocketAddress::StringToIP(info->IpAddressList.IpAddress.String),
+        SocketAddress::StringToIP(info->GatewayList.IpAddress.String)));
+    network->set_ignored(IsIgnoredNetwork(*network));
+    if (include_ignored || !network->ignored()) {
+      networks->push_back(network.release());
+    }
+  }
+
+  return true;
+}
+#endif  // WIN32
+
+bool NetworkManager::IsIgnoredNetwork(const Network& network) {
+#ifdef POSIX
+  // Ignore local networks (lo, lo0, etc)
+  // Also filter out VMware interfaces, typically named vmnet1 and vmnet8
+  if (strncmp(network.name().c_str(), "lo", 2) == 0 ||
+      strncmp(network.name().c_str(), "vmnet", 5) == 0) {
+    return true;
+  }
+#elif defined(WIN32)
+  // Ignore any HOST side vmware adapters with a description like:
+  // VMware Virtual Ethernet Adapter for VMnet1
+  // but don't ignore any GUEST side adapters with a description like:
+  // VMware Accelerated AMD PCNet Adapter #2
+  if (strstr(network.description().c_str(), "VMnet") != NULL) {
+    return true;
+  }
+#endif
+
+  // Ignore any networks with a 0.x.y.z IP
+  return (network.ip() < 0x01000000);
+}
+
+bool NetworkManager::EnumNetworks(bool include_ignored,
+                                  std::vector<Network*>* result) {
+  return CreateNetworks(include_ignored, result);
+}
+
+
+Network::Network(const std::string& name, const std::string& desc,
+                 uint32 ip, uint32 gateway_ip)
+    : name_(name), description_(desc), ip_(ip), gateway_ip_(gateway_ip),
+      ignored_(false), uniform_numerator_(0), uniform_denominator_(0),
+      exponential_numerator_(0), exponential_denominator_(0),
+      quality_(kDefaultQuality) {
+  last_data_time_ = Time();
+
+  // TODO: seed the historical data with one data point based
+  // on the link speed metric from XP (4.0 if < 50, 3.0 otherwise).
+}
+
+void Network::StartSession(NetworkSession* session) {
+  assert(std::find(sessions_.begin(), sessions_.end(), session) ==
+         sessions_.end());
+  sessions_.push_back(session);
+}
+
+void Network::StopSession(NetworkSession* session) {
+  SessionList::iterator iter =
+      std::find(sessions_.begin(), sessions_.end(), session);
+  if (iter != sessions_.end())
+    sessions_.erase(iter);
+}
+
+void Network::EstimateQuality() {
+  uint32 now = Time();
+
+  // Add new data points for the current time.
+  for (uint32 i = 0; i < sessions_.size(); ++i) {
+    if (sessions_[i]->HasQuality())
+      AddDataPoint(now, sessions_[i]->GetCurrentQuality());
+  }
+
+  // Construct the weighted average using both uniform and exponential weights.
+
+  double exp_shift = exp(-kLambda * (now - last_data_time_));
+  double numerator = uniform_numerator_ + exp_shift * exponential_numerator_;
+  double denominator = uniform_denominator_ + exp_shift *
+                       exponential_denominator_;
+
+  if (denominator < DBL_EPSILON)
+    quality_ = kDefaultQuality;
+  else
+    quality_ = numerator / denominator;
+}
+
+std::string Network::ToString() const {
+  std::stringstream ss;
+  // Print out the first space-terminated token of the network desc, plus
+  // the IP address.
+  ss << "Net[" << description_.substr(0, description_.find(' '))
+     << ":" << SocketAddress::IPToString(ip_) << "]";
+  return ss.str();
+}
+
+void Network::AddDataPoint(uint32 time, double quality) {
+  uniform_numerator_ += kAlpha * quality;
+  uniform_denominator_ += kAlpha;
+
+  double exp_shift = exp(-kLambda * (time - last_data_time_));
+  exponential_numerator_ = (1 - kAlpha) * quality + exp_shift *
+                           exponential_numerator_;
+  exponential_denominator_ = (1 - kAlpha) + exp_shift *
+                             exponential_denominator_;
+
+  last_data_time_ = time;
+}
+
+std::string Network::GetState() const {
+  StrMap map;
+  map["lt"] = talk_base::ToString<uint32>(last_data_time_);
+  map["un"] = talk_base::ToString<double>(uniform_numerator_);
+  map["ud"] = talk_base::ToString<double>(uniform_denominator_);
+  map["en"] = talk_base::ToString<double>(exponential_numerator_);
+  map["ed"] = talk_base::ToString<double>(exponential_denominator_);
+
+  std::string str;
+  BuildMap(map, str);
+  return str;
+}
+
+void Network::SetState(const std::string& str) {
+  StrMap map;
+  ParseMap(str, map);
+
+  last_data_time_ = FromString<uint32>(map["lt"]);
+  uniform_numerator_ = FromString<double>(map["un"]);
+  uniform_denominator_ = FromString<double>(map["ud"]);
+  exponential_numerator_ = FromString<double>(map["en"]);
+  exponential_denominator_ = FromString<double>(map["ed"]);
+}
+
+}  // namespace talk_base
diff --git a/talk/base/network.h b/talk/base/network.h
new file mode 100644
index 0000000..8153e54
--- /dev/null
+++ b/talk/base/network.h
@@ -0,0 +1,170 @@
+/*
+ * 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_BASE_NETWORK_H_
+#define TALK_BASE_NETWORK_H_
+
+#include <deque>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "talk/base/basictypes.h"
+
+namespace talk_base {
+
+class Network;
+class NetworkSession;
+
+// Keeps track of the available network interfaces over time so that quality
+// information can be aggregated and recorded.
+class NetworkManager {
+ public:
+  virtual ~NetworkManager();
+
+  // Updates and returns the current list of networks available on this machine.
+  // This version will make sure that repeated calls return the same object for
+  // a given network, so that quality is tracked appropriately.
+  // Does not include ignored networks.
+  bool GetNetworks(std::vector<Network*>* networks);
+
+  // Logs the available networks.
+  void DumpNetworks(bool include_ignored);
+
+  // Reads and writes the state of the quality database in a string format.
+  std::string GetState() const;
+  void SetState(const std::string& str);
+
+  // Creates a network object for each network available on the machine.
+  static bool CreateNetworks(bool include_ignored,
+                             std::vector<Network*>* networks);
+  // Determines if a network should be ignored.
+  static bool IsIgnoredNetwork(const Network& network);
+
+ protected:
+  // Fills the supplied list with all usable networks. Overrideable.
+  virtual bool EnumNetworks(bool include_ignored,
+                            std::vector<Network*>* networks);
+
+ private:
+  typedef std::map<std::string, Network*> NetworkMap;
+
+  NetworkMap networks_;
+};
+
+// Represents a Unix-type network interface, with a name and single address.
+// It also includes the ability to track and estimate quality.
+class Network {
+ public:
+  Network(const std::string& name, const std::string& description,
+          uint32 ip, uint32 gateway_ip);
+
+  // Returns the index of this network.  This is considered the primary key
+  // that identifies each network.
+  const std::string& name() const { return name_; }
+
+  // Returns the OS-assigned name for this network. This is useful for
+  // debugging but should not be sent over the wire (for privacy reasons).
+  const std::string& description() const { return description_; }
+
+  // Identifies the current IP address used by this network.
+  uint32 ip() const { return ip_; }
+  void set_ip(uint32 ip) { ip_ = ip; }
+
+  // Identifies the current gateway IP address used by this network.
+  uint32 gateway_ip() const { return gateway_ip_; }
+  void set_gateway_ip(uint32 ip) { gateway_ip_ = ip; }
+
+  // Indicates whether this network should be ignored, perhaps because the
+  // IP/gateway is 0, or the interface is one we know is invalid.
+  bool ignored() const { return ignored_; }
+  void set_ignored(bool ignored) { ignored_ = ignored; }
+
+  // Updates the list of sessions that are ongoing.
+  void StartSession(NetworkSession* session);
+  void StopSession(NetworkSession* session);
+
+  // Re-computes the estimate of near-future quality based on the information
+  // as of this exact moment.
+  void EstimateQuality();
+
+  // Returns the current estimate of the near-future quality of connections
+  // that use this local interface.
+  double quality() { return quality_; }
+
+  // Debugging description of this network
+  std::string ToString() const;
+
+ private:
+  typedef std::vector<NetworkSession*> SessionList;
+
+  std::string name_;
+  std::string description_;
+  uint32 ip_;
+  uint32 gateway_ip_;
+  bool ignored_;
+  SessionList sessions_;
+  double uniform_numerator_;
+  double uniform_denominator_;
+  double exponential_numerator_;
+  double exponential_denominator_;
+  uint32 last_data_time_;
+  double quality_;
+
+  // Updates the statistics maintained to include the given estimate.
+  void AddDataPoint(uint32 time, double quality);
+
+  // Converts the internal state to and from a string.  This is used to record
+  // quality information into a permanent store.
+  void SetState(const std::string& str);
+  std::string GetState() const;
+
+  friend class NetworkManager;
+};
+
+// Represents a session that is in progress using a particular network and can
+// provide data about the quality of the network at any given moment.
+class NetworkSession {
+ public:
+  virtual ~NetworkSession() { }
+
+  // Determines whether this session has an estimate at this moment.  We will
+  // only call GetCurrentQuality when this returns true.
+  virtual bool HasQuality() = 0;
+
+  // Returns an estimate of the quality at this exact moment.  The result should
+  // be a MOS (mean opinion score) value.
+  virtual float GetCurrentQuality() = 0;
+};
+
+const double QUALITY_BAD  = 3.0;
+const double QUALITY_FAIR = 3.35;
+const double QUALITY_GOOD = 3.7;
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_NETWORK_H_
diff --git a/talk/base/openssladapter.cc b/talk/base/openssladapter.cc
new file mode 100644
index 0000000..2aacecc
--- /dev/null
+++ b/talk/base/openssladapter.cc
@@ -0,0 +1,866 @@
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif  // HAVE_CONFIG_H
+
+#if HAVE_OPENSSL_SSL_H
+
+#include <openssl/bio.h>
+#include <openssl/crypto.h>
+#include <openssl/err.h>
+#include <openssl/opensslv.h>
+#include <openssl/rand.h>
+#include <openssl/ssl.h>
+#include <openssl/x509v3.h>
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/openssladapter.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/Equifax_Secure_Global_eBusiness_CA-1.h"
+
+// TODO: Use a nicer abstraction for mutex.
+
+#if defined(WIN32)
+  #define MUTEX_TYPE HANDLE
+  #define MUTEX_SETUP(x) (x) = CreateMutex(NULL, FALSE, NULL)
+  #define MUTEX_CLEANUP(x) CloseHandle(x)
+  #define MUTEX_LOCK(x) WaitForSingleObject((x), INFINITE)
+  #define MUTEX_UNLOCK(x) ReleaseMutex(x)
+  #define THREAD_ID GetCurrentThreadId()
+#elif defined(_POSIX_THREADS)
+  // _POSIX_THREADS is normally defined in unistd.h if pthreads are available
+  // on your platform.
+  #define MUTEX_TYPE pthread_mutex_t
+  #define MUTEX_SETUP(x) pthread_mutex_init(&(x), NULL)
+  #define MUTEX_CLEANUP(x) pthread_mutex_destroy(&(x))
+  #define MUTEX_LOCK(x) pthread_mutex_lock(&(x))
+  #define MUTEX_UNLOCK(x) pthread_mutex_unlock(&(x))
+  #define THREAD_ID pthread_self()
+#else
+  #error You must define mutex operations appropriate for your platform!
+#endif
+
+struct CRYPTO_dynlock_value {
+  MUTEX_TYPE mutex;
+};
+
+//////////////////////////////////////////////////////////////////////
+// SocketBIO
+//////////////////////////////////////////////////////////////////////
+
+static int socket_write(BIO* h, const char* buf, int num);
+static int socket_read(BIO* h, char* buf, int size);
+static int socket_puts(BIO* h, const char* str);
+static long socket_ctrl(BIO* h, int cmd, long arg1, void* arg2);
+static int socket_new(BIO* h);
+static int socket_free(BIO* data);
+
+static BIO_METHOD methods_socket = {
+	BIO_TYPE_BIO,
+	"socket",
+	socket_write,
+	socket_read,
+	socket_puts,
+	0,
+	socket_ctrl,
+	socket_new,
+	socket_free,
+	NULL,
+};
+
+BIO_METHOD* BIO_s_socket2() { return(&methods_socket); }
+
+BIO* BIO_new_socket(talk_base::AsyncSocket* socket) {
+	BIO* ret = BIO_new(BIO_s_socket2());
+	if (ret == NULL) {
+          return NULL;
+	}
+	ret->ptr = socket;
+	return ret;
+}
+
+static int socket_new(BIO* b) {
+	b->shutdown = 0;
+	b->init = 1;
+	b->num = 0; // 1 means socket closed
+	b->ptr = 0;
+	return 1;
+}
+
+static int socket_free(BIO* b) {
+	if (b == NULL)
+		return 0;
+	return 1;
+}
+
+static int socket_read(BIO* b, char* out, int outl) {
+	if (!out)
+		return -1;
+	talk_base::AsyncSocket* socket = static_cast<talk_base::AsyncSocket*>(b->ptr);
+	BIO_clear_retry_flags(b);
+  int result = socket->Recv(out, outl);
+  if (result > 0) {
+    return result;
+  } else if (result == 0) {
+		b->num = 1;
+  } else if (socket->IsBlocking()) {
+		BIO_set_retry_read(b);
+	}
+	return -1;
+}
+
+static int socket_write(BIO* b, const char* in, int inl) {
+	if (!in)
+		return -1;
+	talk_base::AsyncSocket* socket = static_cast<talk_base::AsyncSocket*>(b->ptr);
+	BIO_clear_retry_flags(b);
+  int result = socket->Send(in, inl);
+  if (result > 0) {
+    return result;
+  } else if (socket->IsBlocking()) {
+		BIO_set_retry_write(b);
+	}
+	return -1;
+}
+
+static int socket_puts(BIO* b, const char* str) {
+	return socket_write(b, str, strlen(str));
+}
+
+static long socket_ctrl(BIO* b, int cmd, long num, void* ptr) {
+  UNUSED(num);
+  UNUSED(ptr);
+
+	switch (cmd) {
+	case BIO_CTRL_RESET:
+		return 0;
+	case BIO_CTRL_EOF:
+		return b->num;
+	case BIO_CTRL_WPENDING:
+	case BIO_CTRL_PENDING:
+		return 0;
+	case BIO_CTRL_FLUSH:
+		return 1;
+	default:
+		return 0;
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// OpenSSLAdapter
+/////////////////////////////////////////////////////////////////////////////
+
+namespace talk_base {
+
+// This array will store all of the mutexes available to OpenSSL.
+static MUTEX_TYPE* mutex_buf = NULL;
+
+static void locking_function(int mode, int n, const char * file, int line) {
+  if (mode & CRYPTO_LOCK) {
+    MUTEX_LOCK(mutex_buf[n]);
+  } else {
+    MUTEX_UNLOCK(mutex_buf[n]);
+  }
+}
+
+static pthread_t id_function() {
+  return THREAD_ID;
+}
+
+static CRYPTO_dynlock_value* dyn_create_function(const char* file, int line) {
+  CRYPTO_dynlock_value* value = new CRYPTO_dynlock_value;
+  if (!value)
+    return NULL;
+  MUTEX_SETUP(value->mutex);
+  return value;
+}
+
+static void dyn_lock_function(int mode, CRYPTO_dynlock_value* l,
+                              const char* file, int line) {
+  if (mode & CRYPTO_LOCK) {
+    MUTEX_LOCK(l->mutex);
+  } else {
+    MUTEX_UNLOCK(l->mutex);
+  }
+}
+
+static void dyn_destroy_function(CRYPTO_dynlock_value* l,
+                                 const char* file, int line) {
+  MUTEX_CLEANUP(l->mutex);
+  delete l;
+}
+
+VerificationCallback OpenSSLAdapter::custom_verify_callback_ = NULL;
+
+bool OpenSSLAdapter::InitializeSSL(VerificationCallback callback) {
+  if (!InitializeSSLThread() || !SSL_library_init())
+  	  return false;
+  SSL_load_error_strings();
+  ERR_load_BIO_strings();
+  OpenSSL_add_all_algorithms();
+  RAND_poll();
+  custom_verify_callback_ = callback;
+  return true;
+}
+
+bool OpenSSLAdapter::InitializeSSLThread() {
+  mutex_buf = new MUTEX_TYPE[CRYPTO_num_locks()];
+  if (!mutex_buf)
+    return false;
+  for (int i = 0; i < CRYPTO_num_locks(); ++i)
+    MUTEX_SETUP(mutex_buf[i]);
+
+  // we need to cast our id_function to return an unsigned long -- pthread_t is a pointer
+  CRYPTO_set_id_callback((unsigned long (*)())id_function);
+  CRYPTO_set_locking_callback(locking_function);
+  CRYPTO_set_dynlock_create_callback(dyn_create_function);
+  CRYPTO_set_dynlock_lock_callback(dyn_lock_function);
+  CRYPTO_set_dynlock_destroy_callback(dyn_destroy_function);
+  return true;
+}
+
+bool OpenSSLAdapter::CleanupSSL() {
+  if (!mutex_buf)
+    return false;
+  CRYPTO_set_id_callback(NULL);
+  CRYPTO_set_locking_callback(NULL);
+  CRYPTO_set_dynlock_create_callback(NULL);
+  CRYPTO_set_dynlock_lock_callback(NULL);
+  CRYPTO_set_dynlock_destroy_callback(NULL);
+  for (int i = 0; i < CRYPTO_num_locks(); ++i)
+    MUTEX_CLEANUP(mutex_buf[i]);
+  delete [] mutex_buf;
+  mutex_buf = NULL;
+  return true;
+}
+
+OpenSSLAdapter::OpenSSLAdapter(AsyncSocket* socket)
+  : SSLAdapter(socket),
+    state_(SSL_NONE),
+    ssl_read_needs_write_(false),
+    ssl_write_needs_read_(false),
+    restartable_(false),
+    ssl_(NULL), ssl_ctx_(NULL),
+    custom_verification_succeeded_(false) {
+}
+
+OpenSSLAdapter::~OpenSSLAdapter() {
+  Cleanup();
+}
+
+int
+OpenSSLAdapter::StartSSL(const char* hostname, bool restartable) {
+  if (state_ != SSL_NONE)
+    return -1;
+
+  ssl_host_name_ = hostname;
+  restartable_ = restartable;
+
+  if (socket_->GetState() != Socket::CS_CONNECTED) {
+    state_ = SSL_WAIT;
+    return 0;
+  }
+
+  state_ = SSL_CONNECTING;
+  if (int err = BeginSSL()) {
+    Error("BeginSSL", err, false);
+    return err;
+  }
+
+  return 0;
+}
+
+int
+OpenSSLAdapter::BeginSSL() {
+  LOG(LS_INFO) << "BeginSSL: " << ssl_host_name_;
+  ASSERT(state_ == SSL_CONNECTING);
+
+  int err = 0;
+  BIO* bio = NULL;
+
+  // First set up the context
+  if (!ssl_ctx_)
+    ssl_ctx_ = SetupSSLContext();
+
+  if (!ssl_ctx_) {
+    err = -1;
+    goto ssl_error;
+  }
+
+  bio = BIO_new_socket(static_cast<AsyncSocketAdapter*>(socket_));
+  if (!bio) {
+    err = -1;
+    goto ssl_error;
+  }
+
+  ssl_ = SSL_new(ssl_ctx_);
+  if (!ssl_) {
+    err = -1;
+    goto ssl_error;
+  }
+
+  SSL_set_app_data(ssl_, this);
+
+  SSL_set_bio(ssl_, bio, bio);
+  SSL_set_mode(ssl_, SSL_MODE_ENABLE_PARTIAL_WRITE |
+                     SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
+
+  // the SSL object owns the bio now
+  bio = NULL;
+
+  // Do the connect
+  err = ContinueSSL();
+  if (err != 0)
+    goto ssl_error;
+
+  return err;
+
+ssl_error:
+  Cleanup();
+  if (bio)
+    BIO_free(bio);
+
+  return err;
+}
+
+int
+OpenSSLAdapter::ContinueSSL() {
+  LOG(LS_INFO) << "ContinueSSL";
+  ASSERT(state_ == SSL_CONNECTING);
+
+  int code = SSL_connect(ssl_);
+  switch (SSL_get_error(ssl_, code)) {
+  case SSL_ERROR_NONE:
+    LOG(LS_INFO) << " -- success";
+
+    if (!SSLPostConnectionCheck(ssl_, ssl_host_name_.c_str())) {
+      LOG(LS_ERROR) << "TLS post connection check failed";
+      // make sure we close the socket
+      Cleanup();
+      // The connect failed so return -1 to shut down the socket
+      return -1;
+    }
+
+    state_ = SSL_CONNECTED;
+    AsyncSocketAdapter::OnConnectEvent(this);
+#if 0  // TODO: worry about this
+    // Don't let ourselves go away during the callbacks
+    PRefPtr<OpenSSLAdapter> lock(this);
+    LOG(LS_INFO) << " -- onStreamReadable";
+    AsyncSocketAdapter::OnReadEvent(this);
+    LOG(LS_INFO) << " -- onStreamWriteable";
+    AsyncSocketAdapter::OnWriteEvent(this);
+#endif
+    break;
+
+  case SSL_ERROR_WANT_READ:
+    LOG(LS_INFO) << " -- error want read";
+    break;
+
+  case SSL_ERROR_WANT_WRITE:
+    LOG(LS_INFO) << " -- error want write";
+    break;
+
+  case SSL_ERROR_ZERO_RETURN:
+  default:
+    LOG(LS_INFO) << " -- error " << code;
+    return (code != 0) ? code : -1;
+  }
+
+  return 0;
+}
+
+void
+OpenSSLAdapter::Error(const char* context, int err, bool signal) {
+  LOG(LS_WARNING) << "SChannelAdapter::Error("
+                  << context << ", " << err << ")";
+  state_ = SSL_ERROR;
+  SetError(err);
+  if (signal)
+    AsyncSocketAdapter::OnCloseEvent(this, err);
+}
+
+void
+OpenSSLAdapter::Cleanup() {
+  LOG(LS_INFO) << "Cleanup";
+
+  state_ = SSL_NONE;
+  ssl_read_needs_write_ = false;
+  ssl_write_needs_read_ = false;
+  custom_verification_succeeded_ = false;
+
+  if (ssl_) {
+    SSL_free(ssl_);
+    ssl_ = NULL;
+  }
+
+  if (ssl_ctx_) {
+    SSL_CTX_free(ssl_ctx_);
+    ssl_ctx_ = NULL;
+  }
+}
+
+//
+// AsyncSocket Implementation
+//
+
+int
+OpenSSLAdapter::Send(const void* pv, size_t cb) {
+  //LOG(LS_INFO) << "OpenSSLAdapter::Send(" << cb << ")";
+
+  switch (state_) {
+  case SSL_NONE:
+    return AsyncSocketAdapter::Send(pv, cb);
+
+  case SSL_WAIT:
+  case SSL_CONNECTING:
+    SetError(EWOULDBLOCK);
+    return SOCKET_ERROR;
+
+  case SSL_CONNECTED:
+    break;
+
+  case SSL_ERROR:
+  default:
+    return SOCKET_ERROR;
+  }
+
+  // OpenSSL will return an error if we try to write zero bytes
+  if (cb == 0)
+    return 0;
+
+  ssl_write_needs_read_ = false;
+
+  int code = SSL_write(ssl_, pv, cb);
+  switch (SSL_get_error(ssl_, code)) {
+  case SSL_ERROR_NONE:
+    //LOG(LS_INFO) << " -- success";
+    return code;
+  case SSL_ERROR_WANT_READ:
+    //LOG(LS_INFO) << " -- error want read";
+    ssl_write_needs_read_ = true;
+    SetError(EWOULDBLOCK);
+    break;
+  case SSL_ERROR_WANT_WRITE:
+    //LOG(LS_INFO) << " -- error want write";
+    SetError(EWOULDBLOCK);
+    break;
+  case SSL_ERROR_ZERO_RETURN:
+    //LOG(LS_INFO) << " -- remote side closed";
+    SetError(EWOULDBLOCK);
+    // do we need to signal closure?
+    break;
+  default:
+    //LOG(LS_INFO) << " -- error " << code;
+    Error("SSL_write", (code ? code : -1), false);
+    break;
+  }
+
+  return SOCKET_ERROR;
+}
+
+int
+OpenSSLAdapter::Recv(void* pv, size_t cb) {
+  //LOG(LS_INFO) << "OpenSSLAdapter::Recv(" << cb << ")";
+  switch (state_) {
+
+  case SSL_NONE:
+    return AsyncSocketAdapter::Recv(pv, cb);
+
+  case SSL_WAIT:
+  case SSL_CONNECTING:
+    SetError(EWOULDBLOCK);
+    return SOCKET_ERROR;
+
+  case SSL_CONNECTED:
+    break;
+
+  case SSL_ERROR:
+  default:
+    return SOCKET_ERROR;
+  }
+
+  // Don't trust OpenSSL with zero byte reads
+  if (cb == 0)
+    return 0;
+
+  ssl_read_needs_write_ = false;
+
+  int code = SSL_read(ssl_, pv, cb);
+  switch (SSL_get_error(ssl_, code)) {
+  case SSL_ERROR_NONE:
+    //LOG(LS_INFO) << " -- success";
+    return code;
+  case SSL_ERROR_WANT_READ:
+    //LOG(LS_INFO) << " -- error want read";
+    SetError(EWOULDBLOCK);
+    break;
+  case SSL_ERROR_WANT_WRITE:
+    //LOG(LS_INFO) << " -- error want write";
+    ssl_read_needs_write_ = true;
+    SetError(EWOULDBLOCK);
+    break;
+  case SSL_ERROR_ZERO_RETURN:
+    //LOG(LS_INFO) << " -- remote side closed";
+    SetError(EWOULDBLOCK);
+    // do we need to signal closure?
+    break;
+  default:
+    //LOG(LS_INFO) << " -- error " << code;
+    Error("SSL_read", (code ? code : -1), false);
+    break;
+  }
+
+  return SOCKET_ERROR;
+}
+
+int
+OpenSSLAdapter::Close() {
+  Cleanup();
+  state_ = restartable_ ? SSL_WAIT : SSL_NONE;
+  return AsyncSocketAdapter::Close();
+}
+
+Socket::ConnState
+OpenSSLAdapter::GetState() const {
+  //if (signal_close_)
+  //  return CS_CONNECTED;
+  ConnState state = socket_->GetState();
+  if ((state == CS_CONNECTED)
+      && ((state_ == SSL_WAIT) || (state_ == SSL_CONNECTING)))
+    state = CS_CONNECTING;
+  return state;
+}
+
+void
+OpenSSLAdapter::OnConnectEvent(AsyncSocket* socket) {
+  LOG(LS_INFO) << "OpenSSLAdapter::OnConnectEvent";
+  if (state_ != SSL_WAIT) {
+    ASSERT(state_ == SSL_NONE);
+    AsyncSocketAdapter::OnConnectEvent(socket);
+    return;
+  }
+
+  state_ = SSL_CONNECTING;
+  if (int err = BeginSSL()) {
+    AsyncSocketAdapter::OnCloseEvent(socket, err);
+  }
+}
+
+void
+OpenSSLAdapter::OnReadEvent(AsyncSocket* socket) {
+  //LOG(LS_INFO) << "OpenSSLAdapter::OnReadEvent";
+
+  if (state_ == SSL_NONE) {
+    AsyncSocketAdapter::OnReadEvent(socket);
+    return;
+  }
+
+  if (state_ == SSL_CONNECTING) {
+    if (int err = ContinueSSL()) {
+      Error("ContinueSSL", err);
+    }
+    return;
+  }
+
+  if (state_ != SSL_CONNECTED)
+    return;
+
+  // Don't let ourselves go away during the callbacks
+  //PRefPtr<OpenSSLAdapter> lock(this); // TODO: fix this
+  if (ssl_write_needs_read_)  {
+    //LOG(LS_INFO) << " -- onStreamWriteable";
+    AsyncSocketAdapter::OnWriteEvent(socket);
+  }
+
+  //LOG(LS_INFO) << " -- onStreamReadable";
+  AsyncSocketAdapter::OnReadEvent(socket);
+}
+
+void
+OpenSSLAdapter::OnWriteEvent(AsyncSocket* socket) {
+  //LOG(LS_INFO) << "OpenSSLAdapter::OnWriteEvent";
+
+  if (state_ == SSL_NONE) {
+    AsyncSocketAdapter::OnWriteEvent(socket);
+    return;
+  }
+
+  if (state_ == SSL_CONNECTING) {
+    if (int err = ContinueSSL()) {
+      Error("ContinueSSL", err);
+    }
+    return;
+  }
+
+  if (state_ != SSL_CONNECTED)
+    return;
+
+  // Don't let ourselves go away during the callbacks
+  //PRefPtr<OpenSSLAdapter> lock(this); // TODO: fix this
+
+  if (ssl_read_needs_write_)  {
+    //LOG(LS_INFO) << " -- onStreamReadable";
+    AsyncSocketAdapter::OnReadEvent(socket);
+  }
+
+  //LOG(LS_INFO) << " -- onStreamWriteable";
+  AsyncSocketAdapter::OnWriteEvent(socket);
+}
+
+void
+OpenSSLAdapter::OnCloseEvent(AsyncSocket* socket, int err) {
+  LOG(LS_INFO) << "OpenSSLAdapter::OnCloseEvent(" << err << ")";
+  AsyncSocketAdapter::OnCloseEvent(socket, err);
+}
+
+// This code is taken from the "Network Security with OpenSSL"
+// sample in chapter 5
+
+bool OpenSSLAdapter::VerifyServerName(SSL* ssl, const char* host,
+                                      bool ignore_bad_cert) {
+  if (!host)
+    return false;
+
+  // Checking the return from SSL_get_peer_certificate here is not strictly
+  // necessary.  With our setup, it is not possible for it to return
+  // NULL.  However, it is good form to check the return.
+  X509* certificate = SSL_get_peer_certificate(ssl);
+  if (!certificate)
+    return false;
+
+#ifdef _DEBUG
+  {
+    LOG(LS_INFO) << "Certificate from server:";
+    BIO* mem = BIO_new(BIO_s_mem());
+    X509_print_ex(mem, certificate, XN_FLAG_SEP_CPLUS_SPC, X509_FLAG_NO_HEADER);
+    BIO_write(mem, "\0", 1);
+    char* buffer;
+    BIO_get_mem_data(mem, &buffer);
+    LOG(LS_INFO) << buffer;
+    BIO_free(mem);
+
+    char* cipher_description =
+      SSL_CIPHER_description(SSL_get_current_cipher(ssl), NULL, 128);
+    LOG(LS_INFO) << "Cipher: " << cipher_description;
+    OPENSSL_free(cipher_description);
+  }
+#endif
+
+  bool ok = false;
+  int extension_count = X509_get_ext_count(certificate);
+  for (int i = 0; i < extension_count; ++i) {
+    X509_EXTENSION* extension = X509_get_ext(certificate, i);
+    int extension_nid = OBJ_obj2nid(X509_EXTENSION_get_object(extension));
+
+    if (extension_nid == NID_subject_alt_name) {
+#if OPENSSL_VERSION_NUMBER >= 0x10000000L
+      const X509V3_EXT_METHOD* meth = X509V3_EXT_get(extension);
+#else
+      X509V3_EXT_METHOD* meth = X509V3_EXT_get(extension);
+#endif
+      if (!meth)
+        break;
+
+      void* ext_str = NULL;
+
+      // We assign this to a local variable, instead of passing the address
+      // directly to ASN1_item_d2i.
+      // See http://readlist.com/lists/openssl.org/openssl-users/0/4761.html.
+      unsigned char* ext_value_data = extension->value->data;
+
+#if OPENSSL_VERSION_NUMBER >= 0x0090800fL
+      const unsigned char **ext_value_data_ptr =
+          (const_cast<const unsigned char **>(&ext_value_data));
+#else
+      unsigned char **ext_value_data_ptr = &ext_value_data;
+#endif
+
+      if (meth->it) {
+        ext_str = ASN1_item_d2i(NULL, ext_value_data_ptr,
+                                extension->value->length,
+                                ASN1_ITEM_ptr(meth->it));
+      } else {
+        ext_str = meth->d2i(NULL, ext_value_data_ptr, extension->value->length);
+      }
+
+      STACK_OF(CONF_VALUE)* value = meth->i2v(meth, ext_str, NULL);
+      for (int j = 0; j < sk_CONF_VALUE_num(value); ++j) {
+        CONF_VALUE* nval = sk_CONF_VALUE_value(value, j);
+        // The value for nval can contain wildcards
+        if (!strcmp(nval->name, "DNS") && string_match(host, nval->value)) {
+          ok = true;
+          break;
+        }
+      }
+      sk_CONF_VALUE_pop_free(value, X509V3_conf_free);
+      value = NULL;
+
+      if (meth->it) {
+        ASN1_item_free(reinterpret_cast<ASN1_VALUE*>(ext_str), meth->it);
+      } else {
+        meth->ext_free(ext_str);
+      }
+      ext_str = NULL;
+    }
+    if (ok)
+      break;
+  }
+
+  char data[256];
+  X509_name_st* subject;
+  if (!ok
+      && (subject = X509_get_subject_name(certificate))
+      && (X509_NAME_get_text_by_NID(subject, NID_commonName,
+                                    data, sizeof(data)) > 0)) {
+    data[sizeof(data)-1] = 0;
+    if (_stricmp(data, host) == 0)
+      ok = true;
+  }
+
+  X509_free(certificate);
+
+  if (!ok && ignore_bad_cert) {
+    LOG(LS_WARNING) << "TLS certificate check FAILED.  "
+      << "Allowing connection anyway.";
+    ok = true;
+  }
+
+  return ok;
+}
+
+bool OpenSSLAdapter::SSLPostConnectionCheck(SSL* ssl, const char* host) {
+  bool ok = VerifyServerName(ssl, host, ignore_bad_cert());
+
+  if (ok) {
+    ok = (SSL_get_verify_result(ssl) == X509_V_OK ||
+          custom_verification_succeeded_);
+  }
+
+  if (!ok && ignore_bad_cert()) {
+    LOG(LS_INFO) << "Other TLS post connection checks failed.";
+    ok = true;
+  }
+
+  return ok;
+}
+
+#if _DEBUG
+
+// We only use this for tracing and so it is only needed in debug mode
+
+void
+OpenSSLAdapter::SSLInfoCallback(const SSL* s, int where, int ret) {
+  const char* str = "undefined";
+  int w = where & ~SSL_ST_MASK;
+  if (w & SSL_ST_CONNECT) {
+    str = "SSL_connect";
+  } else if (w & SSL_ST_ACCEPT) {
+    str = "SSL_accept";
+  }
+  if (where & SSL_CB_LOOP) {
+    LOG(LS_INFO) <<  str << ":" << SSL_state_string_long(s);
+  } else if (where & SSL_CB_ALERT) {
+    str = (where & SSL_CB_READ) ? "read" : "write";
+    LOG(LS_INFO) <<  "SSL3 alert " << str
+      << ":" << SSL_alert_type_string_long(ret)
+      << ":" << SSL_alert_desc_string_long(ret);
+  } else if (where & SSL_CB_EXIT) {
+    if (ret == 0) {
+      LOG(LS_INFO) << str << ":failed in " << SSL_state_string_long(s);
+    } else if (ret < 0) {
+      LOG(LS_INFO) << str << ":error in " << SSL_state_string_long(s);
+    }
+  }
+}
+
+#endif  // _DEBUG
+
+int
+OpenSSLAdapter::SSLVerifyCallback(int ok, X509_STORE_CTX* store) {
+#if _DEBUG
+  if (!ok) {
+    char data[256];
+    X509* cert = X509_STORE_CTX_get_current_cert(store);
+    int depth = X509_STORE_CTX_get_error_depth(store);
+    int err = X509_STORE_CTX_get_error(store);
+
+    LOG(LS_INFO) << "Error with certificate at depth: " << depth;
+    X509_NAME_oneline(X509_get_issuer_name(cert), data, sizeof(data));
+    LOG(LS_INFO) << "  issuer  = " << data;
+    X509_NAME_oneline(X509_get_subject_name(cert), data, sizeof(data));
+    LOG(LS_INFO) << "  subject = " << data;
+    LOG(LS_INFO) << "  err     = " << err
+      << ":" << X509_verify_cert_error_string(err);
+  }
+#endif
+
+  // Get our stream pointer from the store
+  SSL* ssl = reinterpret_cast<SSL*>(
+                X509_STORE_CTX_get_ex_data(store,
+                  SSL_get_ex_data_X509_STORE_CTX_idx()));
+
+  OpenSSLAdapter* stream =
+    reinterpret_cast<OpenSSLAdapter*>(SSL_get_app_data(ssl));
+
+  if (!ok && custom_verify_callback_) {
+    void* cert =
+        reinterpret_cast<void*>(X509_STORE_CTX_get_current_cert(store));
+    if (custom_verify_callback_(cert)) {
+      stream->custom_verification_succeeded_ = true;
+      LOG(LS_INFO) << "validated certificate using custom callback";
+      ok = true;
+    }
+  }
+
+  if (!ok && stream->ignore_bad_cert()) {
+    LOG(LS_WARNING) << "Ignoring cert error while verifying cert chain";
+    ok = 1;
+  }
+
+  return ok;
+}
+
+bool OpenSSLAdapter::ConfigureTrustedRootCertificates(SSL_CTX* ctx) {
+  // Add the root cert to the SSL context
+  // TODO: this cert appears to be the wrong one.
+#if OPENSSL_VERSION_NUMBER >= 0x0090800fL
+   const unsigned char* cert_buffer
+#else
+   unsigned char* cert_buffer
+#endif
+    = EquifaxSecureGlobalEBusinessCA1_certificate;
+  size_t cert_buffer_len = sizeof(EquifaxSecureGlobalEBusinessCA1_certificate);
+  X509* cert = d2i_X509(NULL, &cert_buffer, cert_buffer_len);
+  if (cert == NULL)
+    return false;
+  bool success = X509_STORE_add_cert(SSL_CTX_get_cert_store(ctx), cert);
+  X509_free(cert);
+  return success;
+}
+
+SSL_CTX*
+OpenSSLAdapter::SetupSSLContext() {
+  SSL_CTX* ctx = SSL_CTX_new(TLSv1_client_method());
+  if (ctx == NULL)
+    return NULL;
+
+  if (!ConfigureTrustedRootCertificates(ctx)) {
+    SSL_CTX_free(ctx);
+    return NULL;
+  }
+
+#ifdef _DEBUG
+  SSL_CTX_set_info_callback(ctx, SSLInfoCallback);
+#endif
+
+  SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, SSLVerifyCallback);
+  SSL_CTX_set_verify_depth(ctx, 4);
+  SSL_CTX_set_cipher_list(ctx, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
+
+  return ctx;
+}
+
+} // namespace talk_base
+
+#endif  // HAVE_OPENSSL_SSL_H
diff --git a/talk/base/openssladapter.h b/talk/base/openssladapter.h
new file mode 100644
index 0000000..c89c292
--- /dev/null
+++ b/talk/base/openssladapter.h
@@ -0,0 +1,105 @@
+/*
+ * 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_BASE_OPENSSLADAPTER_H__
+#define TALK_BASE_OPENSSLADAPTER_H__
+
+#include <string>
+#include "talk/base/ssladapter.h"
+
+typedef struct ssl_st SSL;
+typedef struct ssl_ctx_st SSL_CTX;
+typedef struct x509_store_ctx_st X509_STORE_CTX;
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+
+class OpenSSLAdapter : public SSLAdapter {
+public:
+  static bool InitializeSSL(VerificationCallback callback);
+  static bool InitializeSSLThread();
+  static bool CleanupSSL();
+
+  OpenSSLAdapter(AsyncSocket* socket);
+  virtual ~OpenSSLAdapter();
+
+  virtual int StartSSL(const char* hostname, bool restartable);
+  virtual int Send(const void* pv, size_t cb);
+  virtual int Recv(void* pv, size_t cb);
+  virtual int Close();
+
+  // Note that the socket returns ST_CONNECTING while SSL is being negotiated.
+  virtual ConnState GetState() const;
+
+protected:
+  virtual void OnConnectEvent(AsyncSocket* socket);
+  virtual void OnReadEvent(AsyncSocket* socket);
+  virtual void OnWriteEvent(AsyncSocket* socket);
+  virtual void OnCloseEvent(AsyncSocket* socket, int err);
+
+private:
+  enum SSLState {
+    SSL_NONE, SSL_WAIT, SSL_CONNECTING, SSL_CONNECTED, SSL_ERROR
+  };
+
+  int BeginSSL();
+  int ContinueSSL();
+  void Error(const char* context, int err, bool signal = true);
+  void Cleanup();
+
+  static bool VerifyServerName(SSL* ssl, const char* host,
+                               bool ignore_bad_cert);
+  bool SSLPostConnectionCheck(SSL* ssl, const char* host);
+#if _DEBUG
+  static void SSLInfoCallback(const SSL* s, int where, int ret);
+#endif  // !_DEBUG
+  static int SSLVerifyCallback(int ok, X509_STORE_CTX* store);
+  static VerificationCallback custom_verify_callback_;
+  friend class OpenSSLStreamAdapter;  // for custom_verify_callback_;
+
+  static bool ConfigureTrustedRootCertificates(SSL_CTX* ctx);
+  static SSL_CTX* SetupSSLContext();
+
+  SSLState state_;
+  bool ssl_read_needs_write_;
+  bool ssl_write_needs_read_;
+  // If true, socket will retain SSL configuration after Close.
+  bool restartable_;
+
+  SSL* ssl_;
+  SSL_CTX* ssl_ctx_;
+  std::string ssl_host_name_;
+
+  bool custom_verification_succeeded_;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
+
+#endif // TALK_BASE_OPENSSLADAPTER_H__
diff --git a/talk/base/opensslidentity.cc b/talk/base/opensslidentity.cc
new file mode 100644
index 0000000..a9d94b2
--- /dev/null
+++ b/talk/base/opensslidentity.cc
@@ -0,0 +1,274 @@
+/*
+ * 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 "talk/base/opensslidentity.h"
+
+#include <openssl/ssl.h>
+#include <openssl/bio.h>
+#include <openssl/err.h>
+#include <openssl/pem.h>
+#include <openssl/bn.h>
+#include <openssl/rsa.h>
+#include <openssl/crypto.h>
+
+#include "talk/base/logging.h"
+#include "talk/base/helpers.h"
+
+namespace talk_base {
+
+// We could have exposed a myriad of parameters for the crypto stuff,
+// but keeping it simple seems best.
+
+// Strength of generated keys. Those are RSA.
+static const int KEY_LENGTH = 1024;
+
+// Random bits for certificate serial number
+static const int SERIAL_RAND_BITS = 64;
+
+// Certificate validity lifetime
+static const int CERTIFICATE_LIFETIME = 60*60*24*365;  // one year, arbitrarily
+
+// Generate a key pair. Caller is responsible for freeing the returned object.
+static EVP_PKEY* MakeKey() {
+  LOG(LS_INFO) << "Making key pair";
+  EVP_PKEY* pkey = EVP_PKEY_new();
+#if OPENSSL_VERSION_NUMBER < 0x00908000l
+  // Only RSA_generate_key is available. Use that.
+  RSA* rsa = RSA_generate_key(KEY_LENGTH, 0x10001, NULL, NULL);
+  if (!EVP_PKEY_assign_RSA(pkey, rsa)) {
+    EVP_PKEY_free(pkey);
+    RSA_free(rsa);
+    return NULL;
+  }
+#else
+  // RSA_generate_key is deprecated. Use _ex version.
+  BIGNUM* exponent = BN_new();
+  RSA* rsa = RSA_new();
+  if (!pkey || !exponent || !rsa ||
+      !BN_set_word(exponent, 0x10001) ||  // 65537 RSA exponent
+      !RSA_generate_key_ex(rsa, KEY_LENGTH, exponent, NULL) ||
+      !EVP_PKEY_assign_RSA(pkey, rsa)) {
+    EVP_PKEY_free(pkey);
+    BN_free(exponent);
+    RSA_free(rsa);
+    return NULL;
+  }
+  // ownership of rsa struct was assigned, don't free it.
+  BN_free(exponent);
+#endif
+  LOG(LS_INFO) << "Returning key pair";
+  return pkey;
+}
+
+// Generate a self-signed certificate, with the public key from the
+// given key pair. Caller is responsible for freeing the returned object.
+static X509* MakeCertificate(EVP_PKEY* pkey, const char* common_name) {
+  LOG(LS_INFO) << "Making certificate for " << common_name;
+  X509* x509 = NULL;
+  BIGNUM* serial_number = NULL;
+  X509_NAME* name = NULL;
+
+  if ((x509=X509_new()) == NULL)
+    goto error;
+
+  if (!X509_set_pubkey(x509, pkey))
+    goto error;
+
+  // serial number
+  // temporary reference to serial number inside x509 struct
+  ASN1_INTEGER* asn1_serial_number;
+  if (!(serial_number = BN_new()) ||
+      !BN_pseudo_rand(serial_number, SERIAL_RAND_BITS, 0, 0) ||
+      !(asn1_serial_number = X509_get_serialNumber(x509)) ||
+      !BN_to_ASN1_INTEGER(serial_number, asn1_serial_number))
+    goto error;
+
+  if (!X509_set_version(x509, 0L))  // version 1
+    goto error;
+
+  // There are a lot of possible components for the name entries. In
+  // our P2P SSL mode however, the certificates are pre-exchanged
+  // (through the secure XMPP channel), and so the certificate
+  // identification is arbitrary. It can't be empty, so we set some
+  // arbitrary common_name. Note that this certificate goes out in
+  // clear during SSL negotiation, so there may be a privacy issue in
+  // putting anything recognizable here.
+  if (!(name = X509_NAME_new()) ||
+      !X509_NAME_add_entry_by_NID(name, NID_commonName, MBSTRING_UTF8,
+                                     (unsigned char*)common_name, -1, -1, 0) ||
+      !X509_set_subject_name(x509, name) ||
+      !X509_set_issuer_name(x509, name))
+    goto error;
+
+  if (!X509_gmtime_adj(X509_get_notBefore(x509), 0) ||
+      !X509_gmtime_adj(X509_get_notAfter(x509), CERTIFICATE_LIFETIME))
+    goto error;
+
+  if (!X509_sign(x509, pkey, EVP_sha1()))
+    goto error;
+
+  BN_free(serial_number);
+  X509_NAME_free(name);
+  LOG(LS_INFO) << "Returning certificate";
+  return x509;
+
+ error:
+  BN_free(serial_number);
+  X509_NAME_free(name);
+  X509_free(x509);
+  return NULL;
+}
+
+// This dumps the SSL error stack to the log.
+static void LogSSLErrors(const std::string& prefix) {
+  char error_buf[200];
+  unsigned long err;
+  while ((err = ERR_get_error())) {
+    ERR_error_string_n(err, error_buf, sizeof(error_buf));
+    LOG(LS_ERROR) << prefix << ": " << error_buf << "\n";
+  }
+}
+
+OpenSSLKeyPair* OpenSSLKeyPair::Generate() {
+  EVP_PKEY* pkey = MakeKey();
+  if (!pkey) {
+    LogSSLErrors("Generating key pair");
+    return NULL;
+  }
+  return new OpenSSLKeyPair(pkey);
+}
+
+OpenSSLKeyPair::~OpenSSLKeyPair() {
+  EVP_PKEY_free(pkey_);
+}
+
+void OpenSSLKeyPair::AddReference() {
+  CRYPTO_add(&pkey_->references, 1, CRYPTO_LOCK_EVP_PKEY);
+}
+
+#ifdef _DEBUG
+// Print a certificate to the log, for debugging.
+static void PrintCert(X509* x509) {
+  BIO* temp_memory_bio = BIO_new(BIO_s_mem());
+  if (!temp_memory_bio) {
+    LOG_F(LS_ERROR) << "Failed to allocate temporary memory bio";
+    return;
+  }
+  X509_print_ex(temp_memory_bio, x509, XN_FLAG_SEP_CPLUS_SPC, 0);
+  BIO_write(temp_memory_bio, "\0", 1);
+  char* buffer;
+  BIO_get_mem_data(temp_memory_bio, &buffer);
+  LOG(LS_VERBOSE) << buffer;
+  BIO_free(temp_memory_bio);
+}
+#endif
+
+OpenSSLCertificate* OpenSSLCertificate::Generate(
+    OpenSSLKeyPair* key_pair, const std::string& common_name) {
+  std::string actual_common_name = common_name;
+  if (actual_common_name.empty())
+    // Use a random string, arbitrarily 8chars long.
+    actual_common_name = CreateRandomString(8);
+  X509* x509 = MakeCertificate(key_pair->pkey(), actual_common_name.c_str());
+  if (!x509) {
+    LogSSLErrors("Generating certificate");
+    return NULL;
+  }
+#ifdef _DEBUG
+  PrintCert(x509);
+#endif
+  return new OpenSSLCertificate(x509);
+}
+
+OpenSSLCertificate* OpenSSLCertificate::FromPEMString(
+    const std::string& pem_string, int* pem_length) {
+  BIO* bio = BIO_new_mem_buf(const_cast<char*>(pem_string.c_str()), -1);
+  if (!bio)
+    return NULL;
+  (void)BIO_set_close(bio, BIO_NOCLOSE);
+  BIO_set_mem_eof_return(bio, 0);
+  X509 *x509 = PEM_read_bio_X509(bio, NULL, NULL,
+                                 const_cast<char*>("\0"));
+  char *ptr;
+  int remaining_length = BIO_get_mem_data(bio, &ptr);
+  BIO_free(bio);
+  if (pem_length)
+    *pem_length = pem_string.length() - remaining_length;
+  if (x509)
+    return new OpenSSLCertificate(x509);
+  else
+    return NULL;
+}
+
+OpenSSLCertificate::~OpenSSLCertificate() {
+  X509_free(x509_);
+}
+
+std::string OpenSSLCertificate::ToPEMString() const {
+  BIO* bio = BIO_new(BIO_s_mem());
+  if (!bio)
+    return NULL;
+  if (!PEM_write_bio_X509(bio, x509_)) {
+    BIO_free(bio);
+    return NULL;
+  }
+  BIO_write(bio, "\0", 1);
+  char* buffer;
+  BIO_get_mem_data(bio, &buffer);
+  std::string ret(buffer);
+  BIO_free(bio);
+  return ret;
+}
+
+void OpenSSLCertificate::AddReference() {
+  CRYPTO_add(&x509_->references, 1, CRYPTO_LOCK_X509);
+}
+
+OpenSSLIdentity* OpenSSLIdentity::Generate(const std::string& common_name) {
+  OpenSSLKeyPair *key_pair = OpenSSLKeyPair::Generate();
+  if (key_pair) {
+    OpenSSLCertificate *certificate =
+        OpenSSLCertificate::Generate(key_pair, common_name);
+    if (certificate)
+      return new OpenSSLIdentity(key_pair, certificate);
+    delete key_pair;
+  }
+  LOG(LS_INFO) << "Identity generation failed";
+  return NULL;
+}
+
+bool OpenSSLIdentity::ConfigureIdentity(SSL_CTX* ctx) {
+  // 1 is the documented success return code.
+  if (SSL_CTX_use_certificate(ctx, certificate_->x509()) != 1 ||
+     SSL_CTX_use_PrivateKey(ctx, key_pair_->pkey()) != 1) {
+    LogSSLErrors("Configuring key and certificate");
+    return false;
+  }
+  return true;
+}
+
+}  // talk_base namespace
diff --git a/talk/base/opensslidentity.h b/talk/base/opensslidentity.h
new file mode 100644
index 0000000..7cac419
--- /dev/null
+++ b/talk/base/opensslidentity.h
@@ -0,0 +1,137 @@
+/*
+ * 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 TALK_BASE_OPENSSLIDENTITY_H__
+#define TALK_BASE_OPENSSLIDENTITY_H__
+
+#include <openssl/evp.h>
+#include <openssl/x509.h>
+
+#include <string>
+
+#include "talk/base/common.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sslidentity.h"
+
+typedef struct ssl_ctx_st SSL_CTX;
+
+namespace talk_base {
+
+// OpenSSLKeyPair encapsulates an OpenSSL EVP_PKEY* keypair object,
+// which is reference counted inside the OpenSSL library.
+class OpenSSLKeyPair {
+ public:
+  static OpenSSLKeyPair* Generate();
+
+  virtual ~OpenSSLKeyPair();
+
+  virtual OpenSSLKeyPair* GetReference() {
+    AddReference();
+    return new OpenSSLKeyPair(pkey_);
+  }
+
+  EVP_PKEY* pkey() const { return pkey_; }
+
+ private:
+  explicit OpenSSLKeyPair(EVP_PKEY* pkey) : pkey_(pkey) {
+    ASSERT(pkey_ != NULL);
+  }
+  void AddReference();
+
+  EVP_PKEY* pkey_;
+
+  DISALLOW_EVIL_CONSTRUCTORS(OpenSSLKeyPair);
+};
+
+// OpenSSLCertificate encapsulates an OpenSSL X509* certificate object,
+// which is also reference counted inside the OpenSSL library.
+class OpenSSLCertificate : public SSLCertificate {
+ public:
+  static OpenSSLCertificate* Generate(OpenSSLKeyPair* key_pair,
+                                      const std::string& common_name);
+  static OpenSSLCertificate* FromPEMString(const std::string& pem_string,
+                                           int* pem_length);
+
+  virtual ~OpenSSLCertificate();
+
+  virtual OpenSSLCertificate* GetReference() {
+    AddReference();
+    return new OpenSSLCertificate(x509_);
+  }
+
+  X509* x509() const { return x509_; }
+
+  virtual std::string ToPEMString() const;
+
+ private:
+  explicit OpenSSLCertificate(X509* x509) : x509_(x509) {
+    ASSERT(x509_ != NULL);
+  }
+  void AddReference();
+
+  X509* x509_;
+
+  DISALLOW_EVIL_CONSTRUCTORS(OpenSSLCertificate);
+};
+
+// Holds a keypair and certificate together, and a method to generate
+// them consistently.
+class OpenSSLIdentity : public SSLIdentity {
+ public:
+  static OpenSSLIdentity* Generate(const std::string& common_name);
+
+  virtual ~OpenSSLIdentity() { }
+
+  virtual OpenSSLCertificate& certificate() const {
+    return *certificate_;
+  }
+
+  virtual OpenSSLIdentity* GetReference() {
+    return new OpenSSLIdentity(key_pair_->GetReference(),
+                               certificate_->GetReference());
+  }
+
+  // Configure an SSL context object to use our key and certificate.
+  bool ConfigureIdentity(SSL_CTX* ctx);
+
+ private:
+  OpenSSLIdentity(OpenSSLKeyPair* key_pair,
+                  OpenSSLCertificate* certificate)
+      : key_pair_(key_pair), certificate_(certificate) {
+    ASSERT(key_pair != NULL);
+    ASSERT(certificate != NULL);
+  }
+
+  scoped_ptr<OpenSSLKeyPair> key_pair_;
+  scoped_ptr<OpenSSLCertificate> certificate_;
+
+  DISALLOW_EVIL_CONSTRUCTORS(OpenSSLIdentity);
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_OPENSSLIDENTITY_H__
diff --git a/talk/base/opensslstreamadapter.cc b/talk/base/opensslstreamadapter.cc
new file mode 100644
index 0000000..6a34e13
--- /dev/null
+++ b/talk/base/opensslstreamadapter.cc
@@ -0,0 +1,650 @@
+/*
+ * 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.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif  // HAVE_CONFIG_H
+
+#if HAVE_OPENSSL_SSL_H
+
+#include "talk/base/opensslstreamadapter.h"
+
+#include <openssl/bio.h>
+#include <openssl/crypto.h>
+#include <openssl/err.h>
+#include <openssl/rand.h>
+#include <openssl/ssl.h>
+#include <openssl/x509v3.h>
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/stream.h"
+#include "talk/base/openssladapter.h"
+#include "talk/base/opensslidentity.h"
+#include "talk/base/stringutils.h"
+
+namespace talk_base {
+
+//////////////////////////////////////////////////////////////////////
+// StreamBIO
+//////////////////////////////////////////////////////////////////////
+
+static int stream_write(BIO* h, const char* buf, int num);
+static int stream_read(BIO* h, char* buf, int size);
+static int stream_puts(BIO* h, const char* str);
+static long stream_ctrl(BIO* h, int cmd, long arg1, void* arg2);
+static int stream_new(BIO* h);
+static int stream_free(BIO* data);
+
+static BIO_METHOD methods_stream = {
+  BIO_TYPE_BIO,
+  "stream",
+  stream_write,
+  stream_read,
+  stream_puts,
+  0,
+  stream_ctrl,
+  stream_new,
+  stream_free,
+  NULL,
+};
+
+static BIO_METHOD* BIO_s_stream() { return(&methods_stream); }
+
+static BIO* BIO_new_stream(StreamInterface* stream) {
+  BIO* ret = BIO_new(BIO_s_stream());
+  if (ret == NULL)
+    return NULL;
+  ret->ptr = stream;
+  return ret;
+}
+
+// bio methods return 1 (or at least non-zero) on success and 0 on failure.
+
+static int stream_new(BIO* b) {
+  b->shutdown = 0;
+  b->init = 1;
+  b->num = 0;  // 1 means end-of-stream
+  b->ptr = 0;
+  return 1;
+}
+
+static int stream_free(BIO* b) {
+  if (b == NULL)
+    return 0;
+  return 1;
+}
+
+static int stream_read(BIO* b, char* out, int outl) {
+  if (!out)
+    return -1;
+  StreamInterface* stream = static_cast<StreamInterface*>(b->ptr);
+  BIO_clear_retry_flags(b);
+  size_t read;
+  int error;
+  StreamResult result = stream->Read(out, outl, &read, &error);
+  if (result == SR_SUCCESS) {
+    return read;
+  } else if (result == SR_EOS) {
+    b->num = 1;
+  } else if (result == SR_BLOCK) {
+    BIO_set_retry_read(b);
+  }
+  return -1;
+}
+
+static int stream_write(BIO* b, const char* in, int inl) {
+  if (!in)
+    return -1;
+  StreamInterface* stream = static_cast<StreamInterface*>(b->ptr);
+  BIO_clear_retry_flags(b);
+  size_t written;
+  int error;
+  StreamResult result = stream->Write(in, inl, &written, &error);
+  if (result == SR_SUCCESS) {
+    return written;
+  } else if (result == SR_BLOCK) {
+    BIO_set_retry_write(b);
+  }
+  return -1;
+}
+
+static int stream_puts(BIO* b, const char* str) {
+  return stream_write(b, str, strlen(str));
+}
+
+static long stream_ctrl(BIO* b, int cmd, long num, void* ptr) {
+  UNUSED(num);
+  UNUSED(ptr);
+
+  switch (cmd) {
+    case BIO_CTRL_RESET:
+      return 0;
+    case BIO_CTRL_EOF:
+      return b->num;
+    case BIO_CTRL_WPENDING:
+    case BIO_CTRL_PENDING:
+      return 0;
+    case BIO_CTRL_FLUSH:
+      return 1;
+    default:
+      return 0;
+  }
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// OpenSSLStreamAdapter
+/////////////////////////////////////////////////////////////////////////////
+
+OpenSSLStreamAdapter::OpenSSLStreamAdapter(StreamInterface* stream)
+    : SSLStreamAdapter(stream),
+      state_(SSL_NONE),
+      role_(SSL_CLIENT),
+      ssl_read_needs_write_(false), ssl_write_needs_read_(false),
+      ssl_(NULL), ssl_ctx_(NULL),
+      custom_verification_succeeded_(false) {
+}
+
+OpenSSLStreamAdapter::~OpenSSLStreamAdapter() {
+  Cleanup();
+}
+
+void OpenSSLStreamAdapter::SetIdentity(SSLIdentity* identity) {
+  ASSERT(identity_.get() == NULL);
+  identity_.reset(static_cast<OpenSSLIdentity*>(identity));
+}
+
+void OpenSSLStreamAdapter::SetServerRole() {
+  role_ = SSL_SERVER;
+}
+
+void OpenSSLStreamAdapter::SetPeerCertificate(SSLCertificate* cert) {
+  ASSERT(peer_certificate_.get() == NULL);
+  ASSERT(ssl_server_name_.empty());
+  peer_certificate_.reset(static_cast<OpenSSLCertificate*>(cert));
+}
+
+int OpenSSLStreamAdapter::StartSSLWithServer(const char* server_name) {
+  ASSERT(server_name != NULL && server_name[0] != '\0');
+  ssl_server_name_ = server_name;
+  return StartSSL();
+}
+
+int OpenSSLStreamAdapter::StartSSLWithPeer() {
+  ASSERT(ssl_server_name_.empty());
+  // It is permitted to specify peer_certificate_ only later.
+  return StartSSL();
+}
+
+//
+// StreamInterface Implementation
+//
+
+StreamResult OpenSSLStreamAdapter::Write(const void* data, size_t data_len,
+                                         size_t* written, int* error) {
+  LOG(LS_INFO) << "OpenSSLStreamAdapter::Write(" << data_len << ")";
+
+  switch (state_) {
+  case SSL_NONE:
+    // pass-through in clear text
+    return StreamAdapterInterface::Write(data, data_len, written, error);
+
+  case SSL_WAIT:
+  case SSL_CONNECTING:
+    return SR_BLOCK;
+
+  case SSL_CONNECTED:
+    break;
+
+  case SSL_ERROR:
+  case SSL_CLOSED:
+  default:
+    if (error)
+      *error = ssl_error_code_;
+    return SR_ERROR;
+  }
+
+  // OpenSSL will return an error if we try to write zero bytes
+  if (data_len == 0) {
+    if (written)
+      *written = 0;
+    return SR_SUCCESS;
+  }
+
+  ssl_write_needs_read_ = false;
+
+  int code = SSL_write(ssl_, data, data_len);
+  switch (SSL_get_error(ssl_, code)) {
+  case SSL_ERROR_NONE:
+    LOG(LS_INFO) << " -- success";
+    ASSERT(0 < code && static_cast<unsigned>(code) <= data_len);
+    if (written)
+      *written = code;
+    return SR_SUCCESS;
+  case SSL_ERROR_WANT_READ:
+    LOG(LS_INFO) << " -- error want read";
+    ssl_write_needs_read_ = true;
+    return SR_BLOCK;
+  case SSL_ERROR_WANT_WRITE:
+    LOG(LS_INFO) << " -- error want write";
+    return SR_BLOCK;
+
+  case SSL_ERROR_ZERO_RETURN:
+  default:
+    Error("SSL_write", (code ? code : -1), false);
+    if (error)
+      *error = ssl_error_code_;
+    return SR_ERROR;
+  }
+  // not reached
+}
+
+StreamResult OpenSSLStreamAdapter::Read(void* data, size_t data_len,
+                                        size_t* read, int* error) {
+  LOG(LS_INFO) << "OpenSSLStreamAdapter::Read(" << data_len << ")";
+  switch (state_) {
+    case SSL_NONE:
+      // pass-through in clear text
+      return StreamAdapterInterface::Read(data, data_len, read, error);
+
+    case SSL_WAIT:
+    case SSL_CONNECTING:
+      return SR_BLOCK;
+
+    case SSL_CONNECTED:
+      break;
+
+    case SSL_CLOSED:
+      return SR_EOS;
+
+    case SSL_ERROR:
+    default:
+      if (error)
+        *error = ssl_error_code_;
+      return SR_ERROR;
+  }
+
+  // Don't trust OpenSSL with zero byte reads
+  if (data_len == 0) {
+    if (read)
+      *read = 0;
+    return SR_SUCCESS;
+  }
+
+  ssl_read_needs_write_ = false;
+
+  int code = SSL_read(ssl_, data, data_len);
+  switch (SSL_get_error(ssl_, code)) {
+    case SSL_ERROR_NONE:
+      LOG(LS_INFO) << " -- success";
+      ASSERT(0 < code && static_cast<unsigned>(code) <= data_len);
+      if (read)
+        *read = code;
+      return SR_SUCCESS;
+    case SSL_ERROR_WANT_READ:
+      LOG(LS_INFO) << " -- error want read";
+      return SR_BLOCK;
+    case SSL_ERROR_WANT_WRITE:
+      LOG(LS_INFO) << " -- error want write";
+      ssl_read_needs_write_ = true;
+      return SR_BLOCK;
+    case SSL_ERROR_ZERO_RETURN:
+      LOG(LS_INFO) << " -- remote side closed";
+      return SR_EOS;
+      break;
+    default:
+      LOG(LS_INFO) << " -- error " << code;
+      Error("SSL_read", (code ? code : -1), false);
+      if (error)
+        *error = ssl_error_code_;
+      return SR_ERROR;
+  }
+  // not reached
+}
+
+void OpenSSLStreamAdapter::Close() {
+  Cleanup();
+  ASSERT(state_ == SSL_CLOSED || state_ == SSL_ERROR);
+  StreamAdapterInterface::Close();
+}
+
+StreamState OpenSSLStreamAdapter::GetState() const {
+  switch(state_) {
+    case SSL_WAIT:
+    case SSL_CONNECTING:
+      return SS_OPENING;
+    case SSL_CONNECTED:
+      return SS_OPEN;
+    default:
+      return SS_CLOSED;
+  };
+  // not reached
+}
+
+void OpenSSLStreamAdapter::OnEvent(StreamInterface* stream, int events,
+                                   int err) {
+  int events_to_signal = 0;
+  int signal_error = 0;
+  ASSERT(stream == this->stream());
+  if ((events & SE_OPEN)) {
+    LOG(LS_INFO) << "OpenSSLStreamAdapter::OnEvent SE_OPEN";
+    if (state_ != SSL_WAIT) {
+      ASSERT(state_ == SSL_NONE);
+      events_to_signal |= SE_OPEN;
+    } else {
+      state_ = SSL_CONNECTING;
+      if (int err = BeginSSL()) {
+        Error("BeginSSL", err, true);
+        return;
+      }
+    }
+  }
+  if ((events & (SE_READ|SE_WRITE))) {
+    LOG(LS_INFO) << "OpenSSLStreamAdapter::OnEvent"
+                 << ((events & SE_READ) ? " SE_READ" : "")
+                 << ((events & SE_WRITE) ? " SE_WRITE" : "");
+    if (state_ == SSL_NONE) {
+      events_to_signal |= events & (SE_READ|SE_WRITE);
+    } else if (state_ == SSL_CONNECTING) {
+      if (int err = ContinueSSL()) {
+        Error("ContinueSSL", err, true);
+        return;
+      }
+    } else if (state_ == SSL_CONNECTED) {
+      if (((events & SE_READ) && ssl_write_needs_read_) ||
+          (events & SE_WRITE)) {
+        LOG(LS_INFO) << " -- onStreamWriteable";
+        events_to_signal |= SE_WRITE;
+      }
+      if (((events & SE_WRITE) && ssl_read_needs_write_) ||
+          (events & SE_READ)) {
+        LOG(LS_INFO) << " -- onStreamReadable";
+        events_to_signal |= SE_READ;
+      }
+    }
+  }
+  if ((events & SE_CLOSE)) {
+    LOG(LS_INFO) << "OpenSSLStreamAdapter::OnEvent(SE_CLOSE, " << err << ")";
+    Cleanup();
+    events_to_signal |= SE_CLOSE;
+    // SE_CLOSE is the only event that uses the final parameter to OnEvent().
+    ASSERT(signal_error == 0);
+    signal_error = err;
+  }
+  if(events_to_signal)
+    StreamAdapterInterface::OnEvent(stream, events_to_signal, signal_error);
+}
+
+int OpenSSLStreamAdapter::StartSSL() {
+  ASSERT(state_ == SSL_NONE);
+
+  if (StreamAdapterInterface::GetState() != SS_OPEN) {
+    state_ = SSL_WAIT;
+    return 0;
+  }
+
+  state_ = SSL_CONNECTING;
+  if (int err = BeginSSL()) {
+    Error("BeginSSL", err, false);
+    return err;
+  }
+
+  return 0;
+}
+
+int OpenSSLStreamAdapter::BeginSSL() {
+  ASSERT(state_ == SSL_CONNECTING);
+  // The underlying stream has open. If we are in peer-to-peer mode
+  // then a peer certificate must have been specified by now.
+  ASSERT(!ssl_server_name_.empty() || peer_certificate_.get() != NULL);
+  LOG(LS_INFO) << "BeginSSL: "
+               << (!ssl_server_name_.empty() ? ssl_server_name_ :
+                                               "with peer");
+
+  BIO* bio = NULL;
+
+  // First set up the context
+  ASSERT(ssl_ctx_ == NULL);
+  ssl_ctx_ = SetupSSLContext();
+  if (!ssl_ctx_)
+    return -1;
+
+  bio = BIO_new_stream(static_cast<StreamInterface*>(stream()));
+  if (!bio)
+    return -1;
+
+  ssl_ = SSL_new(ssl_ctx_);
+  if (!ssl_) {
+    BIO_free(bio);
+    return -1;
+  }
+
+  SSL_set_app_data(ssl_, this);
+
+  SSL_set_bio(ssl_, bio, bio);  // the SSL object owns the bio now.
+
+  SSL_set_mode(ssl_, SSL_MODE_ENABLE_PARTIAL_WRITE |
+               SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
+
+  // Do the connect
+  return ContinueSSL();
+}
+
+int OpenSSLStreamAdapter::ContinueSSL() {
+  LOG(LS_INFO) << "ContinueSSL";
+  ASSERT(state_ == SSL_CONNECTING);
+
+  int code = (role_ == SSL_CLIENT) ? SSL_connect(ssl_) : SSL_accept(ssl_);
+  switch (SSL_get_error(ssl_, code)) {
+    case SSL_ERROR_NONE:
+      LOG(LS_INFO) << " -- success";
+
+      if (!SSLPostConnectionCheck(ssl_, ssl_server_name_.c_str(),
+                                  peer_certificate_.get() != NULL
+                                  ? peer_certificate_->x509() : NULL)) {
+        LOG(LS_ERROR) << "TLS post connection check failed";
+        return -1;
+      }
+
+      state_ = SSL_CONNECTED;
+      StreamAdapterInterface::OnEvent(stream(), SE_OPEN|SE_READ|SE_WRITE, 0);
+      break;
+
+    case SSL_ERROR_WANT_READ:
+      LOG(LS_INFO) << " -- error want read";
+      break;
+
+    case SSL_ERROR_WANT_WRITE:
+      LOG(LS_INFO) << " -- error want write";
+      break;
+
+    case SSL_ERROR_ZERO_RETURN:
+    default:
+      LOG(LS_INFO) << " -- error " << code;
+      return (code != 0) ? code : -1;
+  }
+
+  return 0;
+}
+
+void OpenSSLStreamAdapter::Error(const char* context, int err, bool signal) {
+  LOG(LS_WARNING) << "OpenSSLStreamAdapter::Error("
+                  << context << ", " << err << ")";
+  state_ = SSL_ERROR;
+  ssl_error_code_ = err;
+  Cleanup();
+  if (signal)
+    StreamAdapterInterface::OnEvent(stream(), SE_CLOSE, err);
+}
+
+void OpenSSLStreamAdapter::Cleanup() {
+  LOG(LS_INFO) << "Cleanup";
+
+  if (state_ != SSL_ERROR) {
+    state_ = SSL_CLOSED;
+    ssl_error_code_ = 0;
+  }
+
+  if (ssl_) {
+    SSL_free(ssl_);
+    ssl_ = NULL;
+  }
+  if (ssl_ctx_) {
+    SSL_CTX_free(ssl_ctx_);
+    ssl_ctx_ = NULL;
+  }
+  identity_.reset();
+  peer_certificate_.reset();
+}
+
+SSL_CTX* OpenSSLStreamAdapter::SetupSSLContext() {
+  SSL_CTX* ctx = SSL_CTX_new(role_ == SSL_CLIENT ? TLSv1_client_method()
+                             : TLSv1_server_method());
+  if (ctx == NULL)
+    return NULL;
+
+  if (identity_.get() && !identity_->ConfigureIdentity(ctx)) {
+    SSL_CTX_free(ctx);
+    return NULL;
+  }
+
+  if (peer_certificate_.get() == NULL) {  // traditional mode
+    // Add the root cert to the SSL context
+    if(!OpenSSLAdapter::ConfigureTrustedRootCertificates(ctx)) {
+      SSL_CTX_free(ctx);
+      return NULL;
+    }
+  }
+
+  if (peer_certificate_.get() != NULL && role_ == SSL_SERVER)
+    // we must specify which client cert to ask for
+    SSL_CTX_add_client_CA(ctx, peer_certificate_->x509());
+
+#ifdef _DEBUG
+  SSL_CTX_set_info_callback(ctx, OpenSSLAdapter::SSLInfoCallback);
+#endif
+
+  SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER |SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
+                     SSLVerifyCallback);
+  SSL_CTX_set_verify_depth(ctx, 4);
+  SSL_CTX_set_cipher_list(ctx, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
+
+  return ctx;
+}
+
+int OpenSSLStreamAdapter::SSLVerifyCallback(int ok, X509_STORE_CTX* store) {
+#if _DEBUG
+  if (!ok) {
+    char data[256];
+    X509* cert = X509_STORE_CTX_get_current_cert(store);
+    int depth = X509_STORE_CTX_get_error_depth(store);
+    int err = X509_STORE_CTX_get_error(store);
+
+    LOG(LS_INFO) << "Error with certificate at depth: " << depth;
+    X509_NAME_oneline(X509_get_issuer_name(cert), data, sizeof(data));
+    LOG(LS_INFO) << "  issuer  = " << data;
+    X509_NAME_oneline(X509_get_subject_name(cert), data, sizeof(data));
+    LOG(LS_INFO) << "  subject = " << data;
+    LOG(LS_INFO) << "  err     = " << err
+      << ":" << X509_verify_cert_error_string(err);
+  }
+#endif
+
+  // Get our SSL structure from the store
+  SSL* ssl = reinterpret_cast<SSL*>(X509_STORE_CTX_get_ex_data(
+                                        store,
+                                        SSL_get_ex_data_X509_STORE_CTX_idx()));
+
+  OpenSSLStreamAdapter* stream =
+    reinterpret_cast<OpenSSLStreamAdapter*>(SSL_get_app_data(ssl));
+
+  // In peer-to-peer mode, no root cert / certificate authority was
+  // specified, so the libraries knows of no certificate to accept,
+  // and therefore it will necessarily call here on the first cert it
+  // tries to verify.
+  if (!ok && stream->peer_certificate_.get() != NULL) {
+    X509* cert = X509_STORE_CTX_get_current_cert(store);
+    int err = X509_STORE_CTX_get_error(store);
+    // peer-to-peer mode: allow the certificate to be self-signed,
+    // assuming it matches the cert that was specified.
+    if (err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT &&
+        X509_cmp(cert, stream->peer_certificate_->x509()) == 0) {
+      LOG(LS_INFO) << "Accepted self-signed peer certificate authority";
+      ok = 1;
+    }
+  } else if (!ok && OpenSSLAdapter::custom_verify_callback_) {
+    // this applies only in traditional mode
+    void* cert =
+        reinterpret_cast<void*>(X509_STORE_CTX_get_current_cert(store));
+    if (OpenSSLAdapter::custom_verify_callback_(cert)) {
+      stream->custom_verification_succeeded_ = true;
+      LOG(LS_INFO) << "validated certificate using custom callback";
+      ok = 1;
+    }
+  }
+
+  if (!ok && stream->ignore_bad_cert()) {
+    LOG(LS_WARNING) << "Ignoring cert error while verifying cert chain";
+    ok = 1;
+  }
+
+  return ok;
+}
+
+// This code is taken from the "Network Security with OpenSSL"
+// sample in chapter 5
+bool OpenSSLStreamAdapter::SSLPostConnectionCheck(SSL* ssl,
+                                                  const char* server_name,
+                                                  const X509* peer_cert) {
+  ASSERT(server_name != NULL);
+  bool ok;
+  if(server_name[0] != '\0') {  // traditional mode
+    ok = OpenSSLAdapter::VerifyServerName(ssl, server_name, ignore_bad_cert());
+
+    if (ok) {
+      ok = (SSL_get_verify_result(ssl) == X509_V_OK ||
+            custom_verification_succeeded_);
+    }
+  } else {  // peer-to-peer mode
+    ASSERT(peer_cert != NULL);
+    // no server name validation
+    ok = true;
+  }
+
+  if (!ok && ignore_bad_cert()) {
+    LOG(LS_ERROR) << "SSL_get_verify_result(ssl) = "
+                  << SSL_get_verify_result(ssl);
+    LOG(LS_INFO) << "Other TLS post connection checks failed.";
+    ok = true;
+  }
+
+  return ok;
+}
+
+}  // namespace talk_base
+
+#endif  // HAVE_OPENSSL_SSL_H
diff --git a/talk/base/opensslstreamadapter.h b/talk/base/opensslstreamadapter.h
new file mode 100644
index 0000000..16ec751
--- /dev/null
+++ b/talk/base/opensslstreamadapter.h
@@ -0,0 +1,171 @@
+/*
+ * 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 TALK_BASE_OPENSSLSTREAMADAPTER_H__
+#define TALK_BASE_OPENSSLSTREAMADAPTER_H__
+
+#include <string>
+#include "talk/base/sslstreamadapter.h"
+#include "talk/base/opensslidentity.h"
+
+typedef struct ssl_st SSL;
+typedef struct ssl_ctx_st SSL_CTX;
+typedef struct x509_store_ctx_st X509_STORE_CTX;
+
+namespace talk_base {
+
+// This class was written with OpenSSLAdapter (a socket adapter) as a
+// starting point. It has similar structure and functionality, with
+// the peer-to-peer mode added.
+//
+// Static methods to initialize and deinit the SSL library are in
+// OpenSSLAdapter. This class also uses
+// OpenSSLAdapter::custom_verify_callback_ (a static field). These
+// should probably be moved out to a neutral class.
+//
+// In a few cases I have factored out some OpenSSLAdapter code into
+// static methods so it can be reused from this class. Eventually that
+// code should probably be moved to a common support
+// class. Unfortunately there remain a few duplicated sections of
+// code. I have not done more restructuring because I did not want to
+// affect existing code that uses OpenSSLAdapter.
+//
+// This class does not support the SSL connection restart feature
+// present in OpenSSLAdapter. I am not entirely sure how the feature
+// is useful and I am not convinced that it works properly.
+//
+// This implementation is careful to disallow data exchange after an
+// SSL error, and it has an explicit SSL_CLOSED state. It should not
+// be possible to send any data in clear after one of the StartSSL
+// methods has been called.
+
+// Look in sslstreamadapter.h for documentation of the methods.
+
+class OpenSSLIdentity;
+
+///////////////////////////////////////////////////////////////////////////////
+
+class OpenSSLStreamAdapter : public SSLStreamAdapter {
+ public:
+  explicit OpenSSLStreamAdapter(StreamInterface* stream);
+  virtual ~OpenSSLStreamAdapter();
+
+  virtual void SetIdentity(SSLIdentity* identity);
+  virtual void SetServerRole();
+  virtual void SetPeerCertificate(SSLCertificate* cert);
+
+  virtual int StartSSLWithServer(const char* server_name);
+  virtual int StartSSLWithPeer();
+
+  virtual StreamResult Read(void* data, size_t data_len,
+                            size_t* read, int* error);
+  virtual StreamResult Write(const void* data, size_t data_len,
+                             size_t* written, int* error);
+  virtual void Close();
+  virtual StreamState GetState() const;
+
+ protected:
+  virtual void OnEvent(StreamInterface* stream, int events, int err);
+
+ private:
+  enum SSLState {
+    // Before calling one of the StartSSL methods, data flows
+    // in clear text.
+    SSL_NONE,
+    SSL_WAIT,  // waiting for the stream to open to start SSL negotiation
+    SSL_CONNECTING,  // SSL negotiation in progress
+    SSL_CONNECTED,  // SSL stream successfully established
+    SSL_ERROR,  // some SSL error occurred, stream is closed
+    SSL_CLOSED  // Clean close
+  };
+  enum SSLRole {
+    SSL_CLIENT, SSL_SERVER
+  };
+
+  // The following three methods return 0 on success and a negative
+  // error code on failure. The error code may be from OpenSSL or -1
+  // on some other error cases, so it can't really be interpreted
+  // unfortunately.
+
+  // Go from state SSL_NONE to either SSL_CONNECTING or SSL_WAIT,
+  // depending on whether the underlying stream is already open or
+  // not.
+  int StartSSL();
+  // Prepare SSL library, state is SSL_CONNECTING.
+  int BeginSSL();
+  // Perform SSL negotiation steps.
+  int ContinueSSL();
+
+  // Error handler helper. signal is given as true for errors in
+  // asynchronous contexts (when an error method was not returned
+  // through some other method), and in that case an SE_CLOSE event is
+  // raised on the stream with the specified error.
+  // A 0 error means a graceful close, otherwise there is not really enough
+  // context to interpret the error code.
+  void Error(const char* context, int err, bool signal);
+  void Cleanup();
+
+  // SSL library configuration
+  SSL_CTX* SetupSSLContext();
+  // SSL verification check
+  bool SSLPostConnectionCheck(SSL* ssl, const char* server_name,
+                              const X509* peer_cert);
+  // SSL certification verification error handler, called back from
+  // the openssl library. Returns an int interpreted as a boolean in
+  // the C style: zero means verification failure, non-zero means
+  // passed.
+  static int SSLVerifyCallback(int ok, X509_STORE_CTX* store);
+
+
+  SSLState state_;
+  SSLRole role_;
+  int ssl_error_code_;  // valid when state_ == SSL_ERROR or SSL_CLOSED
+  // Whether the SSL negotiation is blocked on needing to read or
+  // write to the wrapped stream.
+  bool ssl_read_needs_write_;
+  bool ssl_write_needs_read_;
+
+  SSL* ssl_;
+  SSL_CTX* ssl_ctx_;
+  // in traditional mode, the server name that the server's certificate
+  // must specify. Empty in peer-to-peer mode.
+  // Our key and certificate, mostly useful in peer-to-peer mode.
+  scoped_ptr<OpenSSLIdentity> identity_;
+  std::string ssl_server_name_;
+  // In peer-to-peer mode, the certificate that the peer must
+  // present. Empty in traditional mode.
+  scoped_ptr<OpenSSLCertificate> peer_certificate_;
+
+  // OpenSSLAdapter::custom_verify_callback_ result
+  bool custom_verification_succeeded_;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_OPENSSLSTREAMADAPTER_H__
diff --git a/talk/base/packetsocketfactory.h b/talk/base/packetsocketfactory.h
new file mode 100644
index 0000000..4d1f982
--- /dev/null
+++ b/talk/base/packetsocketfactory.h
@@ -0,0 +1,57 @@
+/*
+ * 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_BASE_PACKETSOCKETFACTORY_H_
+#define TALK_BASE_PACKETSOCKETFACTORY_H_
+
+#include "talk/base/proxyinfo.h"
+
+namespace talk_base {
+
+class AsyncPacketSocket;
+
+class PacketSocketFactory {
+ public:
+  PacketSocketFactory() { }
+  virtual ~PacketSocketFactory() { }
+
+  virtual AsyncPacketSocket* CreateUdpSocket(
+      const SocketAddress& address, int min_port, int max_port) = 0;
+  virtual AsyncPacketSocket* CreateServerTcpSocket(
+      const SocketAddress& local_address, int min_port, int max_port,
+      bool listen, bool ssl) = 0;
+  virtual AsyncPacketSocket* CreateClientTcpSocket(
+      const SocketAddress& local_address, const SocketAddress& remote_address,
+      const ProxyInfo& proxy_info, const std::string& user_agent, bool ssl) = 0;
+
+ private:
+  DISALLOW_EVIL_CONSTRUCTORS(PacketSocketFactory);
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_PACKETSOCKETFACTORY_H_
diff --git a/talk/base/pathutils.cc b/talk/base/pathutils.cc
new file mode 100644
index 0000000..d56373b
--- /dev/null
+++ b/talk/base/pathutils.cc
@@ -0,0 +1,268 @@
+/*
+ * 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.
+ */
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#include <shellapi.h>
+#include <shlobj.h>
+#include <tchar.h>
+#endif  // WIN32
+
+#include "talk/base/common.h"
+#include "talk/base/fileutils.h"
+#include "talk/base/logging.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/urlencode.h"
+
+namespace talk_base {
+
+std::string const EMPTY_STR = "";
+
+// EXT_DELIM separates a file basename from extension
+const char EXT_DELIM = '.';
+
+// FOLDER_DELIMS separate folder segments and the filename
+const char* const FOLDER_DELIMS = "/\\";
+
+// DEFAULT_FOLDER_DELIM is the preferred delimiter for this platform
+#if WIN32
+const char DEFAULT_FOLDER_DELIM = '\\';
+#else  // !WIN32
+const char DEFAULT_FOLDER_DELIM = '/';
+#endif  // !WIN32
+
+///////////////////////////////////////////////////////////////////////////////
+// Pathname - parsing of pathnames into components, and vice versa
+///////////////////////////////////////////////////////////////////////////////
+
+bool Pathname::IsFolderDelimiter(char ch) {
+  return (NULL != ::strchr(FOLDER_DELIMS, ch));
+}
+
+char Pathname::DefaultFolderDelimiter() {
+  return DEFAULT_FOLDER_DELIM;
+}
+
+Pathname::Pathname()
+    : folder_delimiter_(DEFAULT_FOLDER_DELIM) {
+}
+
+Pathname::Pathname(const std::string& pathname)
+    : folder_delimiter_(DEFAULT_FOLDER_DELIM) {
+  SetPathname(pathname);
+}
+
+Pathname::Pathname(const std::string& folder, const std::string& filename)
+    : folder_delimiter_(DEFAULT_FOLDER_DELIM) {
+  SetPathname(folder, filename);
+}
+
+void Pathname::SetFolderDelimiter(char delimiter) {
+  ASSERT(IsFolderDelimiter(delimiter));
+  folder_delimiter_ = delimiter;
+}
+
+void Pathname::Normalize() {
+  for (size_t i=0; i<folder_.length(); ++i) {
+    if (IsFolderDelimiter(folder_[i])) {
+      folder_[i] = folder_delimiter_;
+    }
+  }
+}
+
+void Pathname::clear() {
+  folder_.clear();
+  basename_.clear();
+  extension_.clear();
+}
+
+bool Pathname::empty() const {
+  return folder_.empty() && basename_.empty() && extension_.empty();
+}
+
+std::string Pathname::pathname() const {
+  std::string pathname(folder_);
+  pathname.append(basename_);
+  pathname.append(extension_);
+  if (pathname.empty()) {
+    // Instead of the empty pathname, return the current working directory.
+    pathname.push_back('.');
+    pathname.push_back(folder_delimiter_);
+  }
+  return pathname;
+}
+
+std::string Pathname::url() const {
+  std::string s = "file:///";
+  for (size_t i=0; i<folder_.length(); ++i) {
+    if (IsFolderDelimiter(folder_[i]))
+      s += '/';
+    else
+      s += folder_[i];
+  }
+  s += basename_;
+  s += extension_;
+  return UrlEncodeStringForOnlyUnsafeChars(s);
+}
+
+void Pathname::SetPathname(const std::string& pathname) {
+  std::string::size_type pos = pathname.find_last_of(FOLDER_DELIMS);
+  if (pos != std::string::npos) {
+    SetFolder(pathname.substr(0, pos + 1));
+    SetFilename(pathname.substr(pos + 1));
+  } else {
+    SetFolder(EMPTY_STR);
+    SetFilename(pathname);
+  }
+}
+
+void Pathname::SetPathname(const std::string& folder,
+                           const std::string& filename) {
+  SetFolder(folder);
+  SetFilename(filename);
+}
+
+void Pathname::AppendPathname(const std::string& pathname) {
+  std::string full_pathname(folder_);
+  full_pathname.append(pathname);
+  SetPathname(full_pathname);
+}
+
+std::string Pathname::folder() const {
+  return folder_;
+}
+
+std::string Pathname::folder_name() const {
+  std::string::size_type pos = std::string::npos;
+  if (folder_.size() >= 2) {
+    pos = folder_.find_last_of(FOLDER_DELIMS, folder_.length() - 2);
+  }
+  if (pos != std::string::npos) {
+    return folder_.substr(pos + 1);
+  } else {
+    return folder_;
+  }
+}
+
+std::string Pathname::parent_folder() const {
+  std::string::size_type pos = std::string::npos;
+  if (folder_.size() >= 2) {
+    pos = folder_.find_last_of(FOLDER_DELIMS, folder_.length() - 2);
+  }
+  if (pos != std::string::npos) {
+    return folder_.substr(0, pos + 1);
+  } else {
+    return EMPTY_STR;
+  }
+}
+
+void Pathname::SetFolder(const std::string& folder) {
+  folder_.assign(folder);
+  // Ensure folder ends in a path delimiter
+  if (!folder_.empty() && !IsFolderDelimiter(folder_[folder_.length()-1])) {
+    folder_.push_back(folder_delimiter_);
+  }
+}
+
+void Pathname::AppendFolder(const std::string& folder) {
+  folder_.append(folder);
+  // Ensure folder ends in a path delimiter
+  if (!folder_.empty() && !IsFolderDelimiter(folder_[folder_.length()-1])) {
+    folder_.push_back(folder_delimiter_);
+  }
+}
+
+std::string Pathname::basename() const {
+  return basename_;
+}
+
+bool Pathname::SetBasename(const std::string& basename) {
+  if(basename.find_first_of(FOLDER_DELIMS) != std::string::npos) {
+    return false;
+  }
+  basename_.assign(basename);
+  return true;
+}
+
+std::string Pathname::extension() const {
+  return extension_;
+}
+
+bool Pathname::SetExtension(const std::string& extension) {
+  if (extension.find_first_of(FOLDER_DELIMS) != std::string::npos ||
+    extension.find_first_of(EXT_DELIM, 1) != std::string::npos) {
+      return false;
+  }
+  extension_.assign(extension);
+  // Ensure extension begins with the extension delimiter
+  if (!extension_.empty() && (extension_[0] != EXT_DELIM)) {
+    extension_.insert(extension_.begin(), EXT_DELIM);
+  }
+  return true;
+} 
+
+std::string Pathname::filename() const {
+  std::string filename(basename_);
+  filename.append(extension_);
+  return filename;
+}
+
+bool Pathname::SetFilename(const std::string& filename) {
+  std::string::size_type pos = filename.rfind(EXT_DELIM);
+  if ((pos == std::string::npos) || (pos == 0)) {
+    return SetExtension(EMPTY_STR) && SetBasename(filename);
+  } else {
+    return SetExtension(filename.substr(pos)) && SetBasename(filename.substr(0, pos));
+  }
+}
+
+#ifdef WIN32
+bool Pathname::GetDrive(char *drive, uint32 bytes) const {
+  return GetDrive(drive, bytes, folder_);
+}
+
+// static 
+bool Pathname::GetDrive(char *drive, uint32 bytes,
+                        const std::string& pathname) {
+  // need at lease 4 bytes to save c:
+  if (bytes < 4 || pathname.size() < 3) {
+    return false;
+  }
+  
+  memcpy(drive, pathname.c_str(), 3);
+  drive[3] = 0; 
+  // sanity checking
+  return (isalpha(drive[0]) &&
+          drive[1] == ':' &&
+          drive[2] == '\\');
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
diff --git a/talk/base/pathutils.h b/talk/base/pathutils.h
new file mode 100644
index 0000000..ab2aacd
--- /dev/null
+++ b/talk/base/pathutils.h
@@ -0,0 +1,180 @@
+/*
+ * 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_BASE_PATHUTILS_H__
+#define TALK_BASE_PATHUTILS_H__
+
+#include <string>
+// Temporary, until deprecated helpers are removed.
+#include "talk/base/fileutils.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// Pathname - parsing of pathnames into components, and vice versa.
+//
+// To establish consistent terminology, a filename never contains a folder
+// component.  A folder never contains a filename.  A pathname may include
+// a folder and/or filename component.  Here are some examples:
+//
+//   pathname()      /home/john/example.txt
+//   folder()        /home/john/
+//   filename()                 example.txt
+//   parent_folder() /home/
+//   folder_name()         john/
+//   basename()                 example
+//   extension()                       .txt
+//
+// Basename may begin, end, and/or include periods, but no folder delimiters.
+// If extension exists, it consists of a period followed by zero or more
+// non-period/non-delimiter characters, and basename is non-empty.
+///////////////////////////////////////////////////////////////////////////////
+
+class Pathname {
+public:
+  // Folder delimiters are slash and backslash
+  static bool IsFolderDelimiter(char ch);
+  static char DefaultFolderDelimiter();
+
+  Pathname();
+  Pathname(const std::string& pathname);
+  Pathname(const std::string& folder, const std::string& filename);
+
+  // Set's the default folder delimiter for this Pathname
+  char folder_delimiter() const { return folder_delimiter_; }
+  void SetFolderDelimiter(char delimiter);
+
+  // Normalize changes all folder delimiters to folder_delimiter()
+  void Normalize();
+
+  // Reset to the empty pathname
+  void clear();
+
+  // Returns true if the pathname is empty.  Note: this->pathname().empty()
+  // is always false.
+  bool empty() const;
+
+  std::string url() const;
+
+  // Returns the folder and filename components.  If the pathname is empty,
+  // returns a string representing the current directory (as a relative path,
+  // i.e., ".").
+  std::string pathname() const;
+  void SetPathname(const std::string& pathname);
+  void SetPathname(const std::string& folder, const std::string& filename);
+
+  // Append pathname to the current folder (if any).  Any existing filename
+  // will be discarded.
+  void AppendPathname(const std::string& pathname);
+
+  std::string folder() const;
+  std::string folder_name() const;
+  std::string parent_folder() const;
+  // SetFolder and AppendFolder will append a folder delimiter, if needed.
+  void SetFolder(const std::string& folder);
+  void AppendFolder(const std::string& folder);
+
+  std::string basename() const;
+  bool SetBasename(const std::string& basename);
+
+  std::string extension() const;
+  // SetExtension will prefix a period, if needed.
+  bool SetExtension(const std::string& extension);
+
+  std::string filename() const;
+  bool SetFilename(const std::string& filename);
+
+#ifdef WIN32
+  bool GetDrive(char *drive, uint32 bytes) const;
+  static bool GetDrive(char *drive, uint32 bytes,const std::string& pathname);
+#endif
+
+private:
+  std::string folder_, basename_, extension_;
+  char folder_delimiter_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Global Helpers (deprecated)
+///////////////////////////////////////////////////////////////////////////////
+
+inline void SetOrganizationName(const std::string& organization) {
+  Filesystem::SetOrganizationName(organization);
+}
+inline void SetApplicationName(const std::string& application) {
+  Filesystem::SetApplicationName(application);
+}
+inline void GetOrganizationName(std::string* organization) {
+  Filesystem::GetOrganizationName(organization);
+}
+inline void GetApplicationName(std::string* application) {
+  Filesystem::GetApplicationName(application);
+}
+inline bool CreateFolder(const Pathname& path) {
+  return Filesystem::CreateFolder(path);
+}
+inline bool FinishPath(Pathname& path, bool create, const std::string& append) {
+  if (!append.empty())
+    path.AppendFolder(append);
+  return !create || CreateFolder(path);
+}
+// Note: this method uses the convention of <temp>/<appname> for the temporary
+// folder.  Filesystem uses <temp>/<exename>.  We will be migrating exclusively
+// to <temp>/<orgname>/<appname> eventually.  Since these are temp folders,
+// it's probably ok to orphan them during the transition.
+inline bool GetTemporaryFolder(Pathname& path, bool create,
+                               const std::string& append) {
+  std::string application_name;
+  Filesystem::GetApplicationName(&application_name);
+  ASSERT(!application_name.empty());
+  return Filesystem::GetTemporaryFolder(path, create, &application_name)
+         && FinishPath(path, create, append);
+}
+inline bool GetAppDataFolder(Pathname& path, bool create,
+                             const std::string& append) {
+  ASSERT(!create); // TODO: Support create flag on Filesystem::GetAppDataFolder.
+  return Filesystem::GetAppDataFolder(&path, true)
+         && FinishPath(path, create, append);
+}
+inline bool CleanupTemporaryFolder() {
+  Pathname path;
+  if (!GetTemporaryFolder(path, false, ""))
+    return false;
+  if (Filesystem::IsAbsent(path))
+    return true;
+  if (!Filesystem::IsTemporaryPath(path)) {
+    ASSERT(false);
+    return false;
+  }
+  return Filesystem::DeleteFolderContents(path);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif // TALK_BASE_PATHUTILS_H__
diff --git a/talk/base/physicalsocketserver.cc b/talk/base/physicalsocketserver.cc
new file mode 100644
index 0000000..9cb5f4f
--- /dev/null
+++ b/talk/base/physicalsocketserver.cc
@@ -0,0 +1,1620 @@
+/*
+ * 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.
+ */
+
+#if defined(_MSC_VER) && _MSC_VER < 1300
+#pragma warning(disable:4786)
+#endif
+
+#include <cassert>
+
+#ifdef POSIX
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <signal.h>
+#endif
+
+#ifdef WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#undef SetPort
+#endif
+
+#include <algorithm>
+#include <map>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/byteorder.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/nethelpers.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/time.h"
+#include "talk/base/winping.h"
+#include "talk/base/win32socketinit.h"
+
+// stm: this will tell us if we are on OSX
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef POSIX
+#include <netinet/tcp.h>  // for TCP_NODELAY
+#define IP_MTU 14 // Until this is integrated from linux/in.h to netinet/in.h
+typedef void* SockOptArg;
+#endif  // POSIX
+
+#ifdef WIN32
+typedef char* SockOptArg;
+#endif
+
+namespace talk_base {
+
+// Standard MTUs, from RFC 1191
+const uint16 PACKET_MAXIMUMS[] = {
+  65535,    // Theoretical maximum, Hyperchannel
+  32000,    // Nothing
+  17914,    // 16Mb IBM Token Ring
+  8166,     // IEEE 802.4
+  //4464,   // IEEE 802.5 (4Mb max)
+  4352,     // FDDI
+  //2048,   // Wideband Network
+  2002,     // IEEE 802.5 (4Mb recommended)
+  //1536,   // Expermental Ethernet Networks
+  //1500,   // Ethernet, Point-to-Point (default)
+  1492,     // IEEE 802.3
+  1006,     // SLIP, ARPANET
+  //576,    // X.25 Networks
+  //544,    // DEC IP Portal
+  //512,    // NETBIOS
+  508,      // IEEE 802/Source-Rt Bridge, ARCNET
+  296,      // Point-to-Point (low delay)
+  68,       // Official minimum
+  0,        // End of list marker
+};
+
+const uint32 IP_HEADER_SIZE = 20;
+const uint32 ICMP_HEADER_SIZE = 8;
+
+class PhysicalSocket : public AsyncSocket, public sigslot::has_slots<> {
+ public:
+  PhysicalSocket(PhysicalSocketServer* ss, SOCKET s = INVALID_SOCKET)
+    : ss_(ss), s_(s), enabled_events_(0), error_(0),
+      state_((s == INVALID_SOCKET) ? CS_CLOSED : CS_CONNECTED),
+      resolver_(NULL) {
+#ifdef WIN32
+    // EnsureWinsockInit() ensures that winsock is initialized. The default
+    // version of this function doesn't do anything because winsock is
+    // initialized by constructor of a static object. If neccessary libjingle
+    // users can link it with a different version of this function by replacing
+    // win32socketinit.cc. See win32socketinit.cc for more details.
+    EnsureWinsockInit();
+#endif
+    if (s_ != INVALID_SOCKET) {
+      enabled_events_ = DE_READ | DE_WRITE;
+
+      int type = SOCK_STREAM;
+      socklen_t len = sizeof(type);
+      VERIFY(0 == getsockopt(s_, SOL_SOCKET, SO_TYPE, (SockOptArg)&type, &len));
+      udp_ = (SOCK_DGRAM == type);
+    }
+  }
+
+  virtual ~PhysicalSocket() {
+    Close();
+  }
+
+  // Creates the underlying OS socket (same as the "socket" function).
+  virtual bool Create(int type) {
+    Close();
+    s_ = ::socket(AF_INET, type, 0);
+    udp_ = (SOCK_DGRAM == type);
+    UpdateLastError();
+    if (udp_)
+      enabled_events_ = DE_READ | DE_WRITE;
+    return s_ != INVALID_SOCKET;
+  }
+
+  SocketAddress GetLocalAddress() const {
+    sockaddr_in addr;
+    socklen_t addrlen = sizeof(addr);
+    int result = ::getsockname(s_, (sockaddr*)&addr, &addrlen);
+    SocketAddress address;
+    if (result >= 0) {
+      ASSERT(addrlen == sizeof(addr));
+      address.FromSockAddr(addr);
+    } else {
+      LOG(LS_WARNING) << "GetLocalAddress: unable to get local addr, socket="
+                      << s_;
+    }
+    return address;
+  }
+
+  SocketAddress GetRemoteAddress() const {
+    sockaddr_in addr;
+    socklen_t addrlen = sizeof(addr);
+    int result = ::getpeername(s_, (sockaddr*)&addr, &addrlen);
+    SocketAddress address;
+    if (result >= 0) {
+      ASSERT(addrlen == sizeof(addr));
+      address.FromSockAddr(addr);
+    } else {
+      LOG(LS_WARNING) << "GetRemoteAddress: unable to get remote addr, socket="
+                      << s_;
+    }
+    return address;
+  }
+
+  int Bind(const SocketAddress& addr) {
+    sockaddr_in saddr;
+    addr.ToSockAddr(&saddr);
+    int err = ::bind(s_, (sockaddr*)&saddr, sizeof(saddr));
+    UpdateLastError();
+#ifdef _DEBUG
+    if (0 == err) {
+      dbg_addr_ = "Bound @ ";
+      dbg_addr_.append(GetLocalAddress().ToString());
+    }
+#endif  // _DEBUG
+    return err;
+  }
+
+  int Connect(const SocketAddress& addr) {
+    // TODO: Implicit creation is required to reconnect...
+    // ...but should we make it more explicit?
+    if ((s_ == INVALID_SOCKET) && !Create(SOCK_STREAM))
+      return SOCKET_ERROR;
+    if (addr.IsUnresolved()) {
+      if (state_ != CS_CLOSED) {
+        SetError(EALREADY);
+        return SOCKET_ERROR;
+      }
+
+      LOG(LS_VERBOSE) << "Resolving addr in PhysicalSocket::Connect";
+      resolver_ = new AsyncResolver();
+      resolver_->set_address(addr);
+      resolver_->SignalWorkDone.connect(this, &PhysicalSocket::OnResolveResult);
+      resolver_->Start();
+      state_ = CS_CONNECTING;
+      return 0;
+    }
+
+    return DoConnect(addr);
+  }
+
+  int DoConnect(const SocketAddress& addr) {
+    sockaddr_in saddr;
+    addr.ToSockAddr(&saddr);
+    int err = ::connect(s_, (sockaddr*)&saddr, sizeof(saddr));
+    UpdateLastError();
+    if (err == 0) {
+      state_ = CS_CONNECTED;
+    } else if (IsBlockingError(error_)) {
+      state_ = CS_CONNECTING;
+      enabled_events_ |= DE_CONNECT;
+    } else {
+      return SOCKET_ERROR;
+    }
+
+    enabled_events_ |= DE_READ | DE_WRITE;
+    return 0;
+  }
+
+  int GetError() const {
+    return error_;
+  }
+
+  void SetError(int error) {
+    error_ = error;
+  }
+
+  ConnState GetState() const {
+    return state_;
+  }
+
+  int GetOption(Option opt, int* value) {
+    int slevel;
+    int sopt;
+    if (TranslateOption(opt, &slevel, &sopt) == -1)
+      return -1;
+    socklen_t optlen = sizeof(*value);
+    int ret = ::getsockopt(s_, slevel, sopt, (SockOptArg)value, &optlen);
+    if (ret != -1 && opt == OPT_DONTFRAGMENT) {
+#ifdef LINUX
+      *value = (*value != IP_PMTUDISC_DONT) ? 1 : 0;
+#endif
+    }
+    return ret;
+  }
+
+  int SetOption(Option opt, int value) {
+    int slevel;
+    int sopt;
+    if (TranslateOption(opt, &slevel, &sopt) == -1)
+      return -1;
+    if (opt == OPT_DONTFRAGMENT) {
+#ifdef LINUX
+      value = (value) ? IP_PMTUDISC_DO : IP_PMTUDISC_DONT;
+#endif
+    }
+    return ::setsockopt(s_, slevel, sopt, (SockOptArg)&value, sizeof(value));
+  }
+
+  int Send(const void *pv, size_t cb) {
+    int sent = ::send(s_, reinterpret_cast<const char *>(pv), (int)cb,
+#ifdef LINUX
+        // Suppress SIGPIPE. Without this, attempting to send on a socket whose
+        // other end is closed will result in a SIGPIPE signal being raised to
+        // our process, which by default will terminate the process, which we
+        // don't want. By specifying this flag, we'll just get the error EPIPE
+        // instead and can handle the error gracefully.
+        MSG_NOSIGNAL
+#else
+        0
+#endif
+        );
+    UpdateLastError();
+    // We have seen minidumps where this may be false.
+    ASSERT(sent <= static_cast<int>(cb));
+    if ((sent < 0) && IsBlockingError(error_)) {
+      enabled_events_ |= DE_WRITE;
+    }
+    return sent;
+  }
+
+  int SendTo(const void *pv, size_t cb, const SocketAddress& addr) {
+    sockaddr_in saddr;
+    addr.ToSockAddr(&saddr);
+    int sent = ::sendto(
+        s_, (const char *)pv, (int)cb,
+#ifdef LINUX
+        // Suppress SIGPIPE. See above for explanation.
+        MSG_NOSIGNAL,
+#else
+        0,
+#endif
+        (sockaddr*)&saddr, sizeof(saddr));
+    UpdateLastError();
+    // We have seen minidumps where this may be false.
+    ASSERT(sent <= static_cast<int>(cb));
+    if ((sent < 0) && IsBlockingError(error_)) {
+      enabled_events_ |= DE_WRITE;
+    }
+    return sent;
+  }
+
+  int Recv(void *pv, size_t cb) {
+    int received = ::recv(s_, (char *)pv, (int)cb, 0);
+    if ((received == 0) && (cb != 0)) {
+      // Note: on graceful shutdown, recv can return 0.  In this case, we
+      // pretend it is blocking, and then signal close, so that simplifying
+      // assumptions can be made about Recv.
+      LOG(LS_WARNING) << "EOF from socket; deferring close event";
+      // Must turn this back on so that the select() loop will notice the close
+      // event.
+      enabled_events_ |= DE_READ;
+      error_ = EWOULDBLOCK;
+      return SOCKET_ERROR;
+    }
+    UpdateLastError();
+    bool success = (received >= 0) || IsBlockingError(error_);
+    if (udp_ || success) {
+      enabled_events_ |= DE_READ;
+    }
+    if (!success) {
+      LOG_F(LS_VERBOSE) << "Error = " << error_;
+    }
+    return received;
+  }
+
+  int RecvFrom(void *pv, size_t cb, SocketAddress *paddr) {
+    sockaddr_in saddr;
+    socklen_t cbAddr = sizeof(saddr);
+    int received = ::recvfrom(s_, (char *)pv, (int)cb, 0, (sockaddr*)&saddr,
+                              &cbAddr);
+    UpdateLastError();
+    if ((received >= 0) && (paddr != NULL))
+      paddr->FromSockAddr(saddr);
+    bool success = (received >= 0) || IsBlockingError(error_);
+    if (udp_ || success) {
+      enabled_events_ |= DE_READ;
+    }
+    if (!success) {
+      LOG_F(LS_VERBOSE) << "Error = " << error_;
+    }
+    return received;
+  }
+
+  int Listen(int backlog) {
+    int err = ::listen(s_, backlog);
+    UpdateLastError();
+    if (err == 0) {
+      state_ = CS_CONNECTING;
+      enabled_events_ |= DE_ACCEPT;
+#ifdef _DEBUG
+      dbg_addr_ = "Listening @ ";
+      dbg_addr_.append(GetLocalAddress().ToString());
+#endif  // _DEBUG
+    }
+    return err;
+  }
+
+  AsyncSocket* Accept(SocketAddress *paddr) {
+    sockaddr_in saddr;
+    socklen_t cbAddr = sizeof(saddr);
+    SOCKET s = ::accept(s_, (sockaddr*)&saddr, &cbAddr);
+    UpdateLastError();
+    if (s == INVALID_SOCKET)
+      return NULL;
+    enabled_events_ |= DE_ACCEPT;
+    if (paddr != NULL)
+      paddr->FromSockAddr(saddr);
+    return ss_->WrapSocket(s);
+  }
+
+  int Close() {
+    if (s_ == INVALID_SOCKET)
+      return 0;
+    int err = ::closesocket(s_);
+    UpdateLastError();
+    s_ = INVALID_SOCKET;
+    state_ = CS_CLOSED;
+    enabled_events_ = 0;
+    if (resolver_) {
+      resolver_->Destroy(false);
+      resolver_ = NULL;
+    }
+    return err;
+  }
+
+  int EstimateMTU(uint16* mtu) {
+    SocketAddress addr = GetRemoteAddress();
+    if (addr.IsAny()) {
+      error_ = ENOTCONN;
+      return -1;
+    }
+
+#if defined(WIN32)
+    // Gets the interface MTU (TTL=1) for the interface used to reach |addr|.
+    WinPing ping;
+    if (!ping.IsValid()) {
+      error_ = EINVAL; // can't think of a better error ID
+      return -1;
+    }
+
+    for (int level = 0; PACKET_MAXIMUMS[level + 1] > 0; ++level) {
+      int32 size = PACKET_MAXIMUMS[level] - IP_HEADER_SIZE - ICMP_HEADER_SIZE;
+      WinPing::PingResult result = ping.Ping(addr.ip(), size, 0, 1, false);
+      if (result == WinPing::PING_FAIL) {
+        error_ = EINVAL; // can't think of a better error ID
+        return -1;
+      } else if (result != WinPing::PING_TOO_LARGE) {
+        *mtu = PACKET_MAXIMUMS[level];
+        return 0;
+      }
+    }
+
+    ASSERT(false);
+    return -1;
+#elif defined(IOS) || defined(OSX)
+    // No simple way to do this on Mac OS X.
+    // SIOCGIFMTU would work if we knew which interface would be used, but
+    // figuring that out is pretty complicated. For now we'll return an error
+    // and let the caller pick a default MTU.
+    error_ = EINVAL;
+    return -1;
+#elif defined(LINUX) || defined(ANDROID)
+    // Gets the path MTU.
+    int value;
+    socklen_t vlen = sizeof(value);
+    int err = getsockopt(s_, IPPROTO_IP, IP_MTU, &value, &vlen);
+    if (err < 0) {
+      UpdateLastError();
+      return err;
+    }
+
+    ASSERT((0 <= value) && (value <= 65536));
+    *mtu = value;
+    return 0;
+#endif
+  }
+
+  SocketServer* socketserver() { return ss_; }
+
+ protected:
+  void OnResolveResult(SignalThread* thread) {
+    if (thread != resolver_) {
+      return;
+    }
+
+    int error = resolver_->error();
+    if (error == 0) {
+      error = DoConnect(resolver_->address());
+    } else {
+      Close();
+    }
+
+    if (error) {
+      error_ = error;
+      SignalCloseEvent(this, error_);
+    }
+  }
+
+  void UpdateLastError() {
+    error_ = LAST_SYSTEM_ERROR;
+  }
+
+  static int TranslateOption(Option opt, int* slevel, int* sopt) {
+    switch (opt) {
+      case OPT_DONTFRAGMENT:
+#ifdef WIN32
+        *slevel = IPPROTO_IP;
+        *sopt = IP_DONTFRAGMENT;
+        break;
+#elif defined(IOS) || defined(OSX) || defined(BSD)
+        LOG(LS_WARNING) << "Socket::OPT_DONTFRAGMENT not supported.";
+        return -1;
+#elif defined(POSIX)
+        *slevel = IPPROTO_IP;
+        *sopt = IP_MTU_DISCOVER;
+        break;
+#endif
+      case OPT_RCVBUF:
+        *slevel = SOL_SOCKET;
+        *sopt = SO_RCVBUF;
+        break;
+      case OPT_SNDBUF:
+        *slevel = SOL_SOCKET;
+        *sopt = SO_SNDBUF;
+        break;
+      case OPT_NODELAY:
+        *slevel = IPPROTO_TCP;
+        *sopt = TCP_NODELAY;
+        break;
+      default:
+        ASSERT(false);
+        return -1;
+    }
+    return 0;
+  }
+
+  PhysicalSocketServer* ss_;
+  SOCKET s_;
+  uint8 enabled_events_;
+  bool udp_;
+  int error_;
+  ConnState state_;
+  AsyncResolver* resolver_;
+
+#ifdef _DEBUG
+  std::string dbg_addr_;
+#endif  // _DEBUG;
+};
+
+#ifdef POSIX
+class EventDispatcher : public Dispatcher {
+ public:
+  EventDispatcher(PhysicalSocketServer* ss) : ss_(ss), fSignaled_(false) {
+    if (pipe(afd_) < 0)
+      LOG(LERROR) << "pipe failed";
+    ss_->Add(this);
+  }
+
+  virtual ~EventDispatcher() {
+    ss_->Remove(this);
+    close(afd_[0]);
+    close(afd_[1]);
+  }
+
+  virtual void Signal() {
+    CritScope cs(&crit_);
+    if (!fSignaled_) {
+      const uint8 b[1] = { 0 };
+      if (VERIFY(1 == write(afd_[1], b, sizeof(b)))) {
+        fSignaled_ = true;
+      }
+    }
+  }
+
+  virtual uint32 GetRequestedEvents() {
+    return DE_READ;
+  }
+
+  virtual void OnPreEvent(uint32 ff) {
+    // It is not possible to perfectly emulate an auto-resetting event with
+    // pipes.  This simulates it by resetting before the event is handled.
+
+    CritScope cs(&crit_);
+    if (fSignaled_) {
+      uint8 b[4];  // Allow for reading more than 1 byte, but expect 1.
+      VERIFY(1 == read(afd_[0], b, sizeof(b)));
+      fSignaled_ = false;
+    }
+  }
+
+  virtual void OnEvent(uint32 ff, int err) {
+    ASSERT(false);
+  }
+
+  virtual int GetDescriptor() {
+    return afd_[0];
+  }
+
+  virtual bool IsDescriptorClosed() {
+    return false;
+  }
+
+ private:
+  PhysicalSocketServer *ss_;
+  int afd_[2];
+  bool fSignaled_;
+  CriticalSection crit_;
+};
+
+// These two classes use the self-pipe trick to deliver POSIX signals to our
+// select loop. This is the only safe, reliable, cross-platform way to do
+// non-trivial things with a POSIX signal in an event-driven program (until
+// proper pselect() implementations become ubiquitous).
+
+class PosixSignalHandler {
+ public:
+  // POSIX only specifies 32 signals, but in principle the system might have
+  // more and the programmer might choose to use them, so we size our array
+  // for 128.
+  static const int kNumPosixSignals = 128;
+
+  static PosixSignalHandler *Instance() { return &instance_; }
+
+  // Returns true if the given signal number is set.
+  bool IsSignalSet(int signum) const {
+    ASSERT(signum < ARRAY_SIZE(received_signal_));
+    if (signum < ARRAY_SIZE(received_signal_)) {
+      return received_signal_[signum];
+    } else {
+      return false;
+    }
+  }
+
+  // Clears the given signal number.
+  void ClearSignal(int signum) {
+    ASSERT(signum < ARRAY_SIZE(received_signal_));
+    if (signum < ARRAY_SIZE(received_signal_)) {
+      received_signal_[signum] = false;
+    }
+  }
+
+  // Returns the file descriptor to monitor for signal events.
+  int GetDescriptor() const {
+    return afd_[0];
+  }
+
+  // This is called directly from our real signal handler, so it must be
+  // signal-handler-safe. That means it cannot assume anything about the
+  // user-level state of the process, since the handler could be executed at any
+  // time on any thread.
+  void OnPosixSignalReceived(int signum) {
+    if (signum >= ARRAY_SIZE(received_signal_)) {
+      // We don't have space in our array for this.
+      return;
+    }
+    // Set a flag saying we've seen this signal.
+    received_signal_[signum] = true;
+    // Notify application code that we got a signal.
+    const uint8 b[1] = { 0 };
+    if (-1 == write(afd_[1], b, sizeof(b))) {
+      // Nothing we can do here. If there's an error somehow then there's
+      // nothing we can safely do from a signal handler.
+      // No, we can't even safely log it.
+      // But, we still have to check the return value here. Otherwise,
+      // GCC 4.4.1 complains ignoring return value. Even (void) doesn't help.
+      return;
+    }
+  }
+
+ private:
+  PosixSignalHandler() {
+    if (pipe(afd_) < 0) {
+      LOG_ERR(LS_ERROR) << "pipe failed";
+      return;
+    }
+    if (fcntl(afd_[0], F_SETFL, O_NONBLOCK) < 0) {
+      LOG_ERR(LS_WARNING) << "fcntl #1 failed";
+    }
+    if (fcntl(afd_[1], F_SETFL, O_NONBLOCK) < 0) {
+      LOG_ERR(LS_WARNING) << "fcntl #2 failed";
+    }
+    memset(const_cast<void *>(static_cast<volatile void *>(received_signal_)),
+           0,
+           sizeof(received_signal_));
+  }
+
+  ~PosixSignalHandler() {
+    int fd1 = afd_[0];
+    int fd2 = afd_[1];
+    // We clobber the stored file descriptor numbers here or else in principle
+    // a signal that happens to be delivered during application termination
+    // could erroneously write a zero byte to an unrelated file handle in
+    // OnPosixSignalReceived() if some other file happens to be opened later
+    // during shutdown and happens to be given the same file descriptor number
+    // as our pipe had. Unfortunately even with this precaution there is still a
+    // race where that could occur if said signal happens to be handled
+    // concurrently with this code and happens to have already read the value of
+    // afd_[1] from memory before we clobber it, but that's unlikely.
+    afd_[0] = -1;
+    afd_[1] = -1;
+    close(fd1);
+    close(fd2);
+  }
+
+  // There is just a single global instance. (Signal handlers do not get any
+  // sort of user-defined void * parameter, so they can't access anything that
+  // isn't global.)
+  static PosixSignalHandler instance_;
+
+  int afd_[2];
+  // These are boolean flags that will be set in our signal handler and read
+  // and cleared from Wait(). There is a race involved in this, but it is
+  // benign. The signal handler sets the flag before signaling the pipe, so
+  // we'll never end up blocking in select() while a flag is still true.
+  // However, if two of the same signal arrive close to each other then it's
+  // possible that the second time the handler may set the flag while it's still
+  // true, meaning that signal will be missed. But the first occurrence of it
+  // will still be handled, so this isn't a problem.
+  // Volatile is not necessary here for correctness, but this data _is_ volatile
+  // so I've marked it as such.
+  volatile uint8 received_signal_[kNumPosixSignals];
+};
+
+PosixSignalHandler PosixSignalHandler::instance_;
+
+class PosixSignalDispatcher : public Dispatcher {
+ public:
+  PosixSignalDispatcher(PhysicalSocketServer *owner) : owner_(owner) {
+    owner_->Add(this);
+  }
+
+  virtual ~PosixSignalDispatcher() {
+    owner_->Remove(this);
+  }
+
+  virtual uint32 GetRequestedEvents() {
+    return DE_READ;
+  }
+
+  virtual void OnPreEvent(uint32 ff) {
+    // Events might get grouped if signals come very fast, so we read out up to
+    // 16 bytes to make sure we keep the pipe empty.
+    uint8 b[16];
+    ssize_t ret = read(GetDescriptor(), b, sizeof(b));
+    if (ret < 0) {
+      LOG_ERR(LS_WARNING) << "Error in read()";
+    } else if (ret == 0) {
+      LOG(LS_WARNING) << "Should have read at least one byte";
+    }
+  }
+
+  virtual void OnEvent(uint32 ff, int err) {
+    for (int signum = 0; signum < PosixSignalHandler::kNumPosixSignals;
+         ++signum) {
+      if (PosixSignalHandler::Instance()->IsSignalSet(signum)) {
+        PosixSignalHandler::Instance()->ClearSignal(signum);
+        HandlerMap::iterator i = handlers_.find(signum);
+        if (i == handlers_.end()) {
+          // This can happen if a signal is delivered to our process at around
+          // the same time as we unset our handler for it. It is not an error
+          // condition, but it's unusual enough to be worth logging.
+          LOG(LS_INFO) << "Received signal with no handler: " << signum;
+        } else {
+          // Otherwise, execute our handler.
+          (*i->second)(signum);
+        }
+      }
+    }
+  }
+
+  virtual int GetDescriptor() {
+    return PosixSignalHandler::Instance()->GetDescriptor();
+  }
+
+  virtual bool IsDescriptorClosed() {
+    return false;
+  }
+
+  void SetHandler(int signum, void (*handler)(int)) {
+    handlers_[signum] = handler;
+  }
+
+  void ClearHandler(int signum) {
+    handlers_.erase(signum);
+  }
+
+  bool HasHandlers() {
+    return !handlers_.empty();
+  }
+
+ private:
+  typedef std::map<int, void (*)(int)> HandlerMap;
+
+  HandlerMap handlers_;
+  // Our owner.
+  PhysicalSocketServer *owner_;
+};
+
+class SocketDispatcher : public Dispatcher, public PhysicalSocket {
+ public:
+  explicit SocketDispatcher(PhysicalSocketServer *ss) : PhysicalSocket(ss) {
+  }
+  SocketDispatcher(SOCKET s, PhysicalSocketServer *ss) : PhysicalSocket(ss, s) {
+  }
+
+  virtual ~SocketDispatcher() {
+    Close();
+  }
+
+  bool Initialize() {
+    ss_->Add(this);
+    fcntl(s_, F_SETFL, fcntl(s_, F_GETFL, 0) | O_NONBLOCK);
+    return true;
+  }
+
+  virtual bool Create(int type) {
+    // Change the socket to be non-blocking.
+    if (!PhysicalSocket::Create(type))
+      return false;
+
+    return Initialize();
+  }
+
+  virtual int GetDescriptor() {
+    return s_;
+  }
+
+  virtual bool IsDescriptorClosed() {
+    // We don't have a reliable way of distinguishing end-of-stream
+    // from readability.  So test on each readable call.  Is this
+    // inefficient?  Probably.
+    char ch;
+    ssize_t res = ::recv(s_, &ch, 1, MSG_PEEK);
+    if (res > 0) {
+      // Data available, so not closed.
+      return false;
+    } else if (res == 0) {
+      // EOF, so closed.
+      return true;
+    } else {  // error
+      switch (errno) {
+        // Returned if we've already closed s_.
+        case EBADF:
+        // Returned during ungraceful peer shutdown.
+        case ECONNRESET:
+          return true;
+        default:
+          // Assume that all other errors are just blocking errors, meaning the
+          // connection is still good but we just can't read from it right now.
+          // This should only happen when connecting (and at most once), because
+          // in all other cases this function is only called if the file
+          // descriptor is already known to be in the readable state. However,
+          // it's not necessary a problem if we spuriously interpret a
+          // "connection lost"-type error as a blocking error, because typically
+          // the next recv() will get EOF, so we'll still eventually notice that
+          // the socket is closed.
+          LOG_ERR(LS_WARNING) << "Assuming benign blocking error";
+          return false;
+      }
+    }
+  }
+
+  virtual uint32 GetRequestedEvents() {
+    return enabled_events_;
+  }
+
+  virtual void OnPreEvent(uint32 ff) {
+    if ((ff & DE_CONNECT) != 0)
+      state_ = CS_CONNECTED;
+    if ((ff & DE_CLOSE) != 0)
+      state_ = CS_CLOSED;
+  }
+
+  virtual void OnEvent(uint32 ff, int err) {
+    if ((ff & DE_READ) != 0) {
+      enabled_events_ &= ~DE_READ;
+      SignalReadEvent(this);
+    }
+    if ((ff & DE_WRITE) != 0) {
+      enabled_events_ &= ~DE_WRITE;
+      SignalWriteEvent(this);
+    }
+    if ((ff & DE_CONNECT) != 0) {
+      enabled_events_ &= ~DE_CONNECT;
+      SignalConnectEvent(this);
+    }
+    if ((ff & DE_ACCEPT) != 0) {
+      enabled_events_ &= ~DE_ACCEPT;
+      SignalReadEvent(this);
+    }
+    if ((ff & DE_CLOSE) != 0) {
+      // The socket is now dead to us, so stop checking it.
+      enabled_events_ = 0;
+      SignalCloseEvent(this, err);
+    }
+  }
+
+  virtual int Close() {
+    if (s_ == INVALID_SOCKET)
+      return 0;
+
+    ss_->Remove(this);
+    return PhysicalSocket::Close();
+  }
+};
+
+class FileDispatcher: public Dispatcher, public AsyncFile {
+ public:
+  FileDispatcher(int fd, PhysicalSocketServer *ss) : ss_(ss), fd_(fd) {
+    set_readable(true);
+
+    ss_->Add(this);
+
+    fcntl(fd_, F_SETFL, fcntl(fd_, F_GETFL, 0) | O_NONBLOCK);
+  }
+
+  virtual ~FileDispatcher() {
+    ss_->Remove(this);
+  }
+
+  SocketServer* socketserver() { return ss_; }
+
+  virtual int GetDescriptor() {
+    return fd_;
+  }
+
+  virtual bool IsDescriptorClosed() {
+    return false;
+  }
+
+  virtual uint32 GetRequestedEvents() {
+    return flags_;
+  }
+
+  virtual void OnPreEvent(uint32 ff) {
+  }
+
+  virtual void OnEvent(uint32 ff, int err) {
+    if ((ff & DE_READ) != 0)
+      SignalReadEvent(this);
+    if ((ff & DE_WRITE) != 0)
+      SignalWriteEvent(this);
+    if ((ff & DE_CLOSE) != 0)
+      SignalCloseEvent(this, err);
+  }
+
+  virtual bool readable() {
+    return (flags_ & DE_READ) != 0;
+  }
+
+  virtual void set_readable(bool value) {
+    flags_ = value ? (flags_ | DE_READ) : (flags_ & ~DE_READ);
+  }
+
+  virtual bool writable() {
+    return (flags_ & DE_WRITE) != 0;
+  }
+
+  virtual void set_writable(bool value) {
+    flags_ = value ? (flags_ | DE_WRITE) : (flags_ & ~DE_WRITE);
+  }
+
+ private:
+  PhysicalSocketServer* ss_;
+  int fd_;
+  int flags_;
+};
+
+AsyncFile* PhysicalSocketServer::CreateFile(int fd) {
+  return new FileDispatcher(fd, this);
+}
+
+#endif // POSIX
+
+#ifdef WIN32
+static uint32 FlagsToEvents(uint32 events) {
+  uint32 ffFD = FD_CLOSE;
+  if (events & DE_READ)
+    ffFD |= FD_READ;
+  if (events & DE_WRITE)
+    ffFD |= FD_WRITE;
+  if (events & DE_CONNECT)
+    ffFD |= FD_CONNECT;
+  if (events & DE_ACCEPT)
+    ffFD |= FD_ACCEPT;
+  return ffFD;
+}
+
+class EventDispatcher : public Dispatcher {
+ public:
+  EventDispatcher(PhysicalSocketServer *ss) : ss_(ss) {
+    hev_ = WSACreateEvent();
+    if (hev_) {
+      ss_->Add(this);
+    }
+  }
+
+  ~EventDispatcher() {
+    if (hev_ != NULL) {
+      ss_->Remove(this);
+      WSACloseEvent(hev_);
+      hev_ = NULL;
+    }
+  }
+
+  virtual void Signal() {
+    if (hev_ != NULL)
+      WSASetEvent(hev_);
+  }
+
+  virtual uint32 GetRequestedEvents() {
+    return 0;
+  }
+
+  virtual void OnPreEvent(uint32 ff) {
+    WSAResetEvent(hev_);
+  }
+
+  virtual void OnEvent(uint32 ff, int err) {
+  }
+
+  virtual WSAEVENT GetWSAEvent() {
+    return hev_;
+  }
+
+  virtual SOCKET GetSocket() {
+    return INVALID_SOCKET;
+  }
+
+  virtual bool CheckSignalClose() { return false; }
+
+private:
+  PhysicalSocketServer* ss_;
+  WSAEVENT hev_;
+};
+
+class SocketDispatcher : public Dispatcher, public PhysicalSocket {
+ public:
+  static int next_id_;
+  int id_;
+  bool signal_close_;
+  int signal_err_;
+
+  SocketDispatcher(PhysicalSocketServer* ss)
+      : PhysicalSocket(ss),
+        id_(0),
+        signal_close_(false) {
+  }
+
+  SocketDispatcher(SOCKET s, PhysicalSocketServer* ss)
+      : PhysicalSocket(ss, s),
+        id_(0),
+        signal_close_(false) {
+  }
+
+  virtual ~SocketDispatcher() {
+    Close();
+  }
+
+  bool Initialize() {
+    ASSERT(s_ != INVALID_SOCKET);
+    // Must be a non-blocking
+    u_long argp = 1;
+    ioctlsocket(s_, FIONBIO, &argp);
+    ss_->Add(this);
+    return true;
+  }
+
+  virtual bool Create(int type) {
+    // Create socket
+    if (!PhysicalSocket::Create(type))
+      return false;
+
+    if (!Initialize())
+      return false;
+
+    do { id_ = ++next_id_; } while (id_ == 0);
+    return true;
+  }
+
+  virtual int Close() {
+    if (s_ == INVALID_SOCKET)
+      return 0;
+
+    id_ = 0;
+    signal_close_ = false;
+    ss_->Remove(this);
+    return PhysicalSocket::Close();
+  }
+
+  virtual uint32 GetRequestedEvents() {
+    return enabled_events_;
+  }
+
+  virtual void OnPreEvent(uint32 ff) {
+    if ((ff & DE_CONNECT) != 0)
+      state_ = CS_CONNECTED;
+    // We set CS_CLOSED from CheckSignalClose.
+  }
+
+  virtual void OnEvent(uint32 ff, int err) {
+    int cache_id = id_;
+    if ((ff & DE_READ) != 0) {
+      enabled_events_ &= ~DE_READ;
+      SignalReadEvent(this);
+    }
+    if (((ff & DE_WRITE) != 0) && (id_ == cache_id)) {
+      enabled_events_ &= ~DE_WRITE;
+      SignalWriteEvent(this);
+    }
+    if (((ff & DE_CONNECT) != 0) && (id_ == cache_id)) {
+      if (ff != DE_CONNECT)
+        LOG(LS_VERBOSE) << "Signalled with DE_CONNECT: " << ff;
+      enabled_events_ &= ~DE_CONNECT;
+#ifdef _DEBUG
+      dbg_addr_ = "Connected @ ";
+      dbg_addr_.append(GetRemoteAddress().ToString());
+#endif  // _DEBUG
+      SignalConnectEvent(this);
+    }
+    if (((ff & DE_ACCEPT) != 0) && (id_ == cache_id)) {
+      enabled_events_ &= ~DE_ACCEPT;
+      SignalReadEvent(this);
+    }
+    if (((ff & DE_CLOSE) != 0) && (id_ == cache_id)) {
+      signal_close_ = true;
+      signal_err_ = err;
+    }
+  }
+
+  virtual WSAEVENT GetWSAEvent() {
+    return WSA_INVALID_EVENT;
+  }
+
+  virtual SOCKET GetSocket() {
+    return s_;
+  }
+
+  virtual bool CheckSignalClose() {
+    if (!signal_close_)
+      return false;
+
+    char ch;
+    if (recv(s_, &ch, 1, MSG_PEEK) > 0)
+      return false;
+
+    state_ = CS_CLOSED;
+    signal_close_ = false;
+    SignalCloseEvent(this, signal_err_);
+    return true;
+  }
+};
+
+int SocketDispatcher::next_id_ = 0;
+
+#endif  // WIN32
+
+// Sets the value of a boolean value to false when signaled.
+class Signaler : public EventDispatcher {
+ public:
+  Signaler(PhysicalSocketServer* ss, bool* pf)
+      : EventDispatcher(ss), pf_(pf) {
+  }
+  virtual ~Signaler() { }
+
+  void OnEvent(uint32 ff, int err) {
+    if (pf_)
+      *pf_ = false;
+  }
+
+ private:
+  bool *pf_;
+};
+
+PhysicalSocketServer::PhysicalSocketServer()
+    : fWait_(false),
+      last_tick_tracked_(0),
+      last_tick_dispatch_count_(0) {
+  signal_wakeup_ = new Signaler(this, &fWait_);
+#ifdef WIN32
+  socket_ev_ = WSACreateEvent();
+#endif
+}
+
+PhysicalSocketServer::~PhysicalSocketServer() {
+#ifdef WIN32
+  WSACloseEvent(socket_ev_);
+#endif
+#ifdef POSIX
+  signal_dispatcher_.reset();
+#endif
+  delete signal_wakeup_;
+  ASSERT(dispatchers_.empty());
+}
+
+void PhysicalSocketServer::WakeUp() {
+  signal_wakeup_->Signal();
+}
+
+Socket* PhysicalSocketServer::CreateSocket(int type) {
+  PhysicalSocket* socket = new PhysicalSocket(this);
+  if (socket->Create(type)) {
+    return socket;
+  } else {
+    delete socket;
+    return 0;
+  }
+}
+
+AsyncSocket* PhysicalSocketServer::CreateAsyncSocket(int type) {
+  SocketDispatcher* dispatcher = new SocketDispatcher(this);
+  if (dispatcher->Create(type)) {
+    return dispatcher;
+  } else {
+    delete dispatcher;
+    return 0;
+  }
+}
+
+AsyncSocket* PhysicalSocketServer::WrapSocket(SOCKET s) {
+  SocketDispatcher* dispatcher = new SocketDispatcher(s, this);
+  if (dispatcher->Initialize()) {
+    return dispatcher;
+  } else {
+    delete dispatcher;
+    return 0;
+  }
+}
+
+void PhysicalSocketServer::Add(Dispatcher *pdispatcher) {
+  CritScope cs(&crit_);
+  // Prevent duplicates. This can cause dead dispatchers to stick around.
+  DispatcherList::iterator pos = std::find(dispatchers_.begin(),
+                                           dispatchers_.end(),
+                                           pdispatcher);
+  if (pos != dispatchers_.end())
+    return;
+  dispatchers_.push_back(pdispatcher);
+}
+
+void PhysicalSocketServer::Remove(Dispatcher *pdispatcher) {
+  CritScope cs(&crit_);
+  DispatcherList::iterator pos = std::find(dispatchers_.begin(),
+                                           dispatchers_.end(),
+                                           pdispatcher);
+  ASSERT(pos != dispatchers_.end());
+  size_t index = pos - dispatchers_.begin();
+  dispatchers_.erase(pos);
+  for (IteratorList::iterator it = iterators_.begin(); it != iterators_.end();
+       ++it) {
+    if (index < **it) {
+      --**it;
+    }
+  }
+}
+
+#ifdef POSIX
+bool PhysicalSocketServer::Wait(int cmsWait, bool process_io) {
+  // Calculate timing information
+
+  struct timeval *ptvWait = NULL;
+  struct timeval tvWait;
+  struct timeval tvStop;
+  if (cmsWait != kForever) {
+    // Calculate wait timeval
+    tvWait.tv_sec = cmsWait / 1000;
+    tvWait.tv_usec = (cmsWait % 1000) * 1000;
+    ptvWait = &tvWait;
+
+    // Calculate when to return in a timeval
+    gettimeofday(&tvStop, NULL);
+    tvStop.tv_sec += tvWait.tv_sec;
+    tvStop.tv_usec += tvWait.tv_usec;
+    if (tvStop.tv_usec >= 1000000) {
+      tvStop.tv_usec -= 1000000;
+      tvStop.tv_sec += 1;
+    }
+  }
+
+  // Zero all fd_sets. Don't need to do this inside the loop since
+  // select() zeros the descriptors not signaled
+
+  fd_set fdsRead;
+  FD_ZERO(&fdsRead);
+  fd_set fdsWrite;
+  FD_ZERO(&fdsWrite);
+
+  fWait_ = true;
+
+  while (fWait_) {
+    int fdmax = -1;
+    {
+      CritScope cr(&crit_);
+      for (size_t i = 0; i < dispatchers_.size(); ++i) {
+        // Query dispatchers for read and write wait state
+        Dispatcher *pdispatcher = dispatchers_[i];
+        ASSERT(pdispatcher);
+        if (!process_io && (pdispatcher != signal_wakeup_))
+          continue;
+        int fd = pdispatcher->GetDescriptor();
+        if (fd > fdmax)
+          fdmax = fd;
+
+        uint32 ff = pdispatcher->GetRequestedEvents();
+        if (ff & (DE_READ | DE_ACCEPT))
+          FD_SET(fd, &fdsRead);
+        if (ff & (DE_WRITE | DE_CONNECT))
+          FD_SET(fd, &fdsWrite);
+      }
+    }
+
+    // Wait then call handlers as appropriate
+    // < 0 means error
+    // 0 means timeout
+    // > 0 means count of descriptors ready
+    int n = select(fdmax + 1, &fdsRead, &fdsWrite, NULL, ptvWait);
+
+    // If error, return error.
+    if (n < 0) {
+      if (errno != EINTR) {
+        LOG_E(LS_ERROR, EN, errno) << "select";
+        return false;
+      }
+      // Else ignore the error and keep going. If this EINTR was for one of the
+      // signals managed by this PhysicalSocketServer, the
+      // PosixSignalDeliveryDispatcher will be in the signaled state in the next
+      // iteration.
+    } else if (n == 0) {
+      // If timeout, return success
+      return true;
+    } else {
+      // We have signaled descriptors
+      CritScope cr(&crit_);
+      for (size_t i = 0; i < dispatchers_.size(); ++i) {
+        Dispatcher *pdispatcher = dispatchers_[i];
+        int fd = pdispatcher->GetDescriptor();
+        uint32 ff = 0;
+        int errcode = 0;
+
+        // Reap any error code, which can be signaled through reads or writes.
+        // TODO: Should we set errcode if getsockopt fails?
+        if (FD_ISSET(fd, &fdsRead) || FD_ISSET(fd, &fdsWrite)) {
+          socklen_t len = sizeof(errcode);
+          ::getsockopt(fd, SOL_SOCKET, SO_ERROR, &errcode, &len);
+        }
+
+        // Check readable descriptors. If we're waiting on an accept, signal
+        // that. Otherwise we're waiting for data, check to see if we're
+        // readable or really closed.
+        // TODO: Only peek at TCP descriptors.
+        if (FD_ISSET(fd, &fdsRead)) {
+          FD_CLR(fd, &fdsRead);
+          if (pdispatcher->GetRequestedEvents() & DE_ACCEPT) {
+            ff |= DE_ACCEPT;
+          } else if (errcode || pdispatcher->IsDescriptorClosed()) {
+            ff |= DE_CLOSE;
+          } else {
+            ff |= DE_READ;
+          }
+        }
+
+        // Check writable descriptors. If we're waiting on a connect, detect
+        // success versus failure by the reaped error code.
+        if (FD_ISSET(fd, &fdsWrite)) {
+          FD_CLR(fd, &fdsWrite);
+          if (pdispatcher->GetRequestedEvents() & DE_CONNECT) {
+            if (!errcode) {
+              ff |= DE_CONNECT;
+            } else {
+              ff |= DE_CLOSE;
+            }
+          } else {
+            ff |= DE_WRITE;
+          }
+        }
+
+        // Tell the descriptor about the event.
+        if (ff != 0) {
+          pdispatcher->OnPreEvent(ff);
+          pdispatcher->OnEvent(ff, errcode);
+        }
+      }
+    }
+
+    // Recalc the time remaining to wait. Doing it here means it doesn't get
+    // calced twice the first time through the loop
+
+    if (cmsWait != kForever) {
+      ptvWait->tv_sec = 0;
+      ptvWait->tv_usec = 0;
+      struct timeval tvT;
+      gettimeofday(&tvT, NULL);
+      if ((tvStop.tv_sec > tvT.tv_sec)
+          || ((tvStop.tv_sec == tvT.tv_sec)
+              && (tvStop.tv_usec > tvT.tv_usec))) {
+        ptvWait->tv_sec = tvStop.tv_sec - tvT.tv_sec;
+        ptvWait->tv_usec = tvStop.tv_usec - tvT.tv_usec;
+        if (ptvWait->tv_usec < 0) {
+          ASSERT(ptvWait->tv_sec > 0);
+          ptvWait->tv_usec += 1000000;
+          ptvWait->tv_sec -= 1;
+        }
+      }
+    }
+  }
+
+  return true;
+}
+
+static void GlobalSignalHandler(int signum) {
+  PosixSignalHandler::Instance()->OnPosixSignalReceived(signum);
+}
+
+bool PhysicalSocketServer::SetPosixSignalHandler(int signum,
+                                                 void (*handler)(int)) {
+  // If handler is SIG_IGN or SIG_DFL then clear our user-level handler,
+  // otherwise set one.
+  if (handler == SIG_IGN || handler == SIG_DFL) {
+    if (!InstallSignal(signum, handler)) {
+      return false;
+    }
+    if (signal_dispatcher_.get()) {
+      signal_dispatcher_->ClearHandler(signum);
+      if (!signal_dispatcher_->HasHandlers()) {
+        signal_dispatcher_.reset();
+      }
+    }
+  } else {
+    if (!signal_dispatcher_.get()) {
+      signal_dispatcher_.reset(new PosixSignalDispatcher(this));
+    }
+    signal_dispatcher_->SetHandler(signum, handler);
+    if (!InstallSignal(signum, &GlobalSignalHandler)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool PhysicalSocketServer::InstallSignal(int signum, void (*handler)(int)) {
+  struct sigaction act;
+  // It doesn't really matter what we set this mask to.
+  if (sigemptyset(&act.sa_mask) != 0) {
+    LOG_ERR(LS_ERROR) << "Couldn't set mask";
+    return false;
+  }
+  act.sa_handler = handler;
+  // Use SA_RESTART so that our syscalls don't get EINTR, since we don't need it
+  // and it's a nuisance. Though some syscalls still return EINTR and there's no
+  // real standard for which ones. :(
+  act.sa_flags = SA_RESTART;
+  if (sigaction(signum, &act, NULL) != 0) {
+    LOG_ERR(LS_ERROR) << "Couldn't set sigaction";
+    return false;
+  }
+  return true;
+}
+#endif  // POSIX
+
+#ifdef WIN32
+bool PhysicalSocketServer::Wait(int cmsWait, bool process_io) {
+  int cmsTotal = cmsWait;
+  int cmsElapsed = 0;
+  uint32 msStart = Time();
+
+#if LOGGING
+  if (last_tick_dispatch_count_ == 0) {
+    last_tick_tracked_ = msStart;
+  }
+#endif
+
+  fWait_ = true;
+  while (fWait_) {
+    std::vector<WSAEVENT> events;
+    std::vector<Dispatcher *> event_owners;
+
+    events.push_back(socket_ev_);
+
+    {
+      CritScope cr(&crit_);
+      size_t i = 0;
+      iterators_.push_back(&i);
+      // Don't track dispatchers_.size(), because we want to pick up any new
+      // dispatchers that were added while processing the loop.
+      while (i < dispatchers_.size()) {
+        Dispatcher* disp = dispatchers_[i++];
+        if (!process_io && (disp != signal_wakeup_))
+          continue;
+        SOCKET s = disp->GetSocket();
+        if (disp->CheckSignalClose()) {
+          // We just signalled close, don't poll this socket
+        } else if (s != INVALID_SOCKET) {
+          WSAEventSelect(s,
+                         events[0],
+                         FlagsToEvents(disp->GetRequestedEvents()));
+        } else {
+          events.push_back(disp->GetWSAEvent());
+          event_owners.push_back(disp);
+        }
+      }
+      ASSERT(iterators_.back() == &i);
+      iterators_.pop_back();
+    }
+
+    // Which is shorter, the delay wait or the asked wait?
+
+    int cmsNext;
+    if (cmsWait == kForever) {
+      cmsNext = cmsWait;
+    } else {
+      cmsNext = _max(0, cmsTotal - cmsElapsed);
+    }
+
+    // Wait for one of the events to signal
+    DWORD dw = WSAWaitForMultipleEvents(static_cast<DWORD>(events.size()),
+                                        &events[0],
+                                        false,
+                                        cmsNext,
+                                        false);
+
+#if 0  // LOGGING
+    // we track this information purely for logging purposes.
+    last_tick_dispatch_count_++;
+    if (last_tick_dispatch_count_ >= 1000) {
+      int32 elapsed = TimeSince(last_tick_tracked_);
+      LOG(INFO) << "PhysicalSocketServer took " << elapsed
+                << "ms for 1000 events";
+
+      // If we get more than 1000 events in a second, we are spinning badly
+      // (normally it should take about 8-20 seconds).
+      ASSERT(elapsed > 1000);
+
+      last_tick_tracked_ = Time();
+      last_tick_dispatch_count_ = 0;
+    }
+#endif
+
+    if (dw == WSA_WAIT_FAILED) {
+      // Failed?
+      // TODO: need a better strategy than this!
+      int error = WSAGetLastError();
+      ASSERT(false);
+      return false;
+    } else if (dw == WSA_WAIT_TIMEOUT) {
+      // Timeout?
+      return true;
+    } else {
+      // Figure out which one it is and call it
+      CritScope cr(&crit_);
+      int index = dw - WSA_WAIT_EVENT_0;
+      if (index > 0) {
+        --index; // The first event is the socket event
+        event_owners[index]->OnPreEvent(0);
+        event_owners[index]->OnEvent(0, 0);
+      } else if (process_io) {
+        size_t i = 0, end = dispatchers_.size();
+        iterators_.push_back(&i);
+        iterators_.push_back(&end);  // Don't iterate over new dispatchers.
+        while (i < end) {
+          Dispatcher* disp = dispatchers_[i++];
+          SOCKET s = disp->GetSocket();
+          if (s == INVALID_SOCKET)
+            continue;
+
+          WSANETWORKEVENTS wsaEvents;
+          int err = WSAEnumNetworkEvents(s, events[0], &wsaEvents);
+          if (err == 0) {
+
+#if LOGGING
+            {
+              if ((wsaEvents.lNetworkEvents & FD_READ) &&
+                  wsaEvents.iErrorCode[FD_READ_BIT] != 0) {
+                LOG(WARNING) << "PhysicalSocketServer got FD_READ_BIT error "
+                             << wsaEvents.iErrorCode[FD_READ_BIT];
+              }
+              if ((wsaEvents.lNetworkEvents & FD_WRITE) &&
+                  wsaEvents.iErrorCode[FD_WRITE_BIT] != 0) {
+                LOG(WARNING) << "PhysicalSocketServer got FD_WRITE_BIT error "
+                             << wsaEvents.iErrorCode[FD_WRITE_BIT];
+              }
+              if ((wsaEvents.lNetworkEvents & FD_CONNECT) &&
+                  wsaEvents.iErrorCode[FD_CONNECT_BIT] != 0) {
+                LOG(WARNING) << "PhysicalSocketServer got FD_CONNECT_BIT error "
+                             << wsaEvents.iErrorCode[FD_CONNECT_BIT];
+              }
+              if ((wsaEvents.lNetworkEvents & FD_ACCEPT) &&
+                  wsaEvents.iErrorCode[FD_ACCEPT_BIT] != 0) {
+                LOG(WARNING) << "PhysicalSocketServer got FD_ACCEPT_BIT error "
+                             << wsaEvents.iErrorCode[FD_ACCEPT_BIT];
+              }
+              if ((wsaEvents.lNetworkEvents & FD_CLOSE) &&
+                  wsaEvents.iErrorCode[FD_CLOSE_BIT] != 0) {
+                LOG(WARNING) << "PhysicalSocketServer got FD_CLOSE_BIT error "
+                             << wsaEvents.iErrorCode[FD_CLOSE_BIT];
+              }
+            }
+#endif
+            uint32 ff = 0;
+            int errcode = 0;
+            if (wsaEvents.lNetworkEvents & FD_READ)
+              ff |= DE_READ;
+            if (wsaEvents.lNetworkEvents & FD_WRITE)
+              ff |= DE_WRITE;
+            if (wsaEvents.lNetworkEvents & FD_CONNECT) {
+              if (wsaEvents.iErrorCode[FD_CONNECT_BIT] == 0) {
+                ff |= DE_CONNECT;
+              } else {
+                ff |= DE_CLOSE;
+                errcode = wsaEvents.iErrorCode[FD_CONNECT_BIT];
+              }
+            }
+            if (wsaEvents.lNetworkEvents & FD_ACCEPT)
+              ff |= DE_ACCEPT;
+            if (wsaEvents.lNetworkEvents & FD_CLOSE) {
+              ff |= DE_CLOSE;
+              errcode = wsaEvents.iErrorCode[FD_CLOSE_BIT];
+            }
+            if (ff != 0) {
+              disp->OnPreEvent(ff);
+              disp->OnEvent(ff, errcode);
+            }
+          }
+        }
+        ASSERT(iterators_.back() == &end);
+        iterators_.pop_back();
+        ASSERT(iterators_.back() == &i);
+        iterators_.pop_back();
+      }
+
+      // Reset the network event until new activity occurs
+      WSAResetEvent(socket_ev_);
+    }
+
+    // Break?
+    if (!fWait_)
+      break;
+    cmsElapsed = TimeSince(msStart);
+    if ((cmsWait != kForever) && (cmsElapsed >= cmsWait)) {
+       break;
+    }
+  }
+
+  // Done
+  return true;
+}
+#endif  // WIN32
+
+}  // namespace talk_base
diff --git a/talk/base/physicalsocketserver.h b/talk/base/physicalsocketserver.h
new file mode 100644
index 0000000..aeb8348
--- /dev/null
+++ b/talk/base/physicalsocketserver.h
@@ -0,0 +1,133 @@
+/*
+ * 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_BASE_PHYSICALSOCKETSERVER_H__
+#define TALK_BASE_PHYSICALSOCKETSERVER_H__
+
+#include <vector>
+
+#include "talk/base/asyncfile.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/socketserver.h"
+#include "talk/base/criticalsection.h"
+
+#ifdef POSIX
+typedef int SOCKET;
+#endif // POSIX
+
+namespace talk_base {
+
+// Event constants for the Dispatcher class.
+enum DispatcherEvent {
+  DE_READ    = 0x0001,
+  DE_WRITE   = 0x0002,
+  DE_CONNECT = 0x0004,
+  DE_CLOSE   = 0x0008,
+  DE_ACCEPT  = 0x0010,
+};
+
+class Signaler;
+#ifdef POSIX
+class PosixSignalDispatcher;
+#endif
+
+class Dispatcher {
+ public:
+  virtual ~Dispatcher() {}
+  virtual uint32 GetRequestedEvents() = 0;
+  virtual void OnPreEvent(uint32 ff) = 0;
+  virtual void OnEvent(uint32 ff, int err) = 0;
+#ifdef WIN32
+  virtual WSAEVENT GetWSAEvent() = 0;
+  virtual SOCKET GetSocket() = 0;
+  virtual bool CheckSignalClose() = 0;
+#elif POSIX
+  virtual int GetDescriptor() = 0;
+  virtual bool IsDescriptorClosed() = 0;
+#endif
+};
+
+// A socket server that provides the real sockets of the underlying OS.
+class PhysicalSocketServer : public SocketServer {
+public:
+  PhysicalSocketServer();
+  virtual ~PhysicalSocketServer();
+
+  // SocketFactory:
+  virtual Socket* CreateSocket(int type);
+  virtual AsyncSocket* CreateAsyncSocket(int type);
+
+  // Internal Factory for Accept
+  AsyncSocket* WrapSocket(SOCKET s);
+
+  // SocketServer:
+  virtual bool Wait(int cms, bool process_io);
+  virtual void WakeUp();
+
+  void Add(Dispatcher* dispatcher);
+  void Remove(Dispatcher* dispatcher);
+
+#ifdef POSIX
+  AsyncFile* CreateFile(int fd);
+
+  // Sets the function to be executed in response to the specified POSIX signal.
+  // The function is executed from inside Wait() using the "self-pipe trick"--
+  // regardless of which thread receives the signal--and hence can safely
+  // manipulate user-level data structures.
+  // "handler" may be SIG_IGN, SIG_DFL, or a user-specified function, just like
+  // with signal(2).
+  // Only one PhysicalSocketServer should have user-level signal handlers.
+  // Dispatching signals on multiple PhysicalSocketServers is not reliable.
+  // The signal mask is not modified. It is the caller's responsibily to
+  // maintain it as desired.
+  bool SetPosixSignalHandler(int signum, void (*handler)(int));
+#endif
+
+private:
+  typedef std::vector<Dispatcher*> DispatcherList;
+  typedef std::vector<size_t*> IteratorList;
+
+#ifdef POSIX
+  static bool InstallSignal(int signum, void (*handler)(int));
+
+  scoped_ptr<PosixSignalDispatcher> signal_dispatcher_;
+#endif
+  DispatcherList dispatchers_;
+  IteratorList iterators_;
+  Signaler* signal_wakeup_;
+  CriticalSection crit_;
+  bool fWait_;
+  uint32 last_tick_tracked_;
+  int last_tick_dispatch_count_;
+#ifdef WIN32
+  WSAEVENT socket_ev_;
+#endif
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_PHYSICALSOCKETSERVER_H__
diff --git a/talk/base/proxydetect.cc b/talk/base/proxydetect.cc
new file mode 100644
index 0000000..66213ee
--- /dev/null
+++ b/talk/base/proxydetect.cc
@@ -0,0 +1,1259 @@
+/*
+ * 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/base/proxydetect.h"
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#include <shlobj.h>
+#endif  // WIN32
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef OSX
+#include <SystemConfiguration/SystemConfiguration.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <CoreServices/CoreServices.h>
+#include <Security/Security.h>
+#include "macconversion.h"
+#endif
+
+#include <map>
+
+#include "talk/base/fileutils.h"
+#include "talk/base/httpcommon.h"
+#include "talk/base/httpcommon-inl.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/stringutils.h"
+
+#ifdef WIN32
+#define _TRY_WINHTTP 1
+#define _TRY_JSPROXY 0
+#define _TRY_WM_FINDPROXY 0
+#define _TRY_IE_LAN_SETTINGS 1
+#endif  // WIN32
+
+// For all platforms try Firefox.
+#define _TRY_FIREFOX 1
+
+// Use profiles.ini to find the correct profile for this user.
+// If not set, we'll just look for the default one.
+#define USE_FIREFOX_PROFILES_INI 1
+
+static const size_t kMaxLineLength = 1024;
+static const char kFirefoxPattern[] = "Firefox";
+static const char kInternetExplorerPattern[] = "MSIE";
+
+struct StringMap {
+ public:
+  void Add(const char * name, const char * value) { map_[name] = value; }
+  const std::string& Get(const char * name, const char * def = "") const {
+    std::map<std::string, std::string>::const_iterator it =
+        map_.find(name);
+    if (it != map_.end())
+      return it->second;
+    def_ = def;
+    return def_;
+  }
+  bool IsSet(const char * name) const {
+    return (map_.find(name) != map_.end());
+  }
+ private:
+  std::map<std::string, std::string> map_;
+  mutable std::string def_;
+};
+
+enum UserAgent {
+  UA_FIREFOX,
+  UA_INTERNETEXPLORER,
+  UA_OTHER,
+  UA_UNKNOWN
+};
+
+#if _TRY_WINHTTP
+//#include <winhttp.h>
+// Note: From winhttp.h
+
+const char WINHTTP[] = "winhttp";
+
+typedef LPVOID HINTERNET;
+
+typedef struct {
+  DWORD  dwAccessType;      // see WINHTTP_ACCESS_* types below
+  LPWSTR lpszProxy;         // proxy server list
+  LPWSTR lpszProxyBypass;   // proxy bypass list
+} WINHTTP_PROXY_INFO, * LPWINHTTP_PROXY_INFO;
+
+typedef struct {
+  DWORD   dwFlags;
+  DWORD   dwAutoDetectFlags;
+  LPCWSTR lpszAutoConfigUrl;
+  LPVOID  lpvReserved;
+  DWORD   dwReserved;
+  BOOL    fAutoLogonIfChallenged;
+} WINHTTP_AUTOPROXY_OPTIONS;
+
+typedef struct {
+  BOOL    fAutoDetect;
+  LPWSTR  lpszAutoConfigUrl;
+  LPWSTR  lpszProxy;
+  LPWSTR  lpszProxyBypass;
+} WINHTTP_CURRENT_USER_IE_PROXY_CONFIG;
+
+extern "C" {
+  typedef HINTERNET (WINAPI * pfnWinHttpOpen)
+      (
+          IN LPCWSTR pwszUserAgent,
+          IN DWORD   dwAccessType,
+          IN LPCWSTR pwszProxyName   OPTIONAL,
+          IN LPCWSTR pwszProxyBypass OPTIONAL,
+          IN DWORD   dwFlags
+          );
+  typedef BOOL (STDAPICALLTYPE * pfnWinHttpCloseHandle)
+      (
+          IN HINTERNET hInternet
+          );
+  typedef BOOL (STDAPICALLTYPE * pfnWinHttpGetProxyForUrl)
+      (
+          IN  HINTERNET                   hSession,
+          IN  LPCWSTR                     lpcwszUrl,
+          IN  WINHTTP_AUTOPROXY_OPTIONS * pAutoProxyOptions,
+          OUT WINHTTP_PROXY_INFO *        pProxyInfo
+          );
+  typedef BOOL (STDAPICALLTYPE * pfnWinHttpGetIEProxyConfig)
+      (
+          IN OUT WINHTTP_CURRENT_USER_IE_PROXY_CONFIG * pProxyConfig
+          );
+
+} // extern "C"
+
+#define WINHTTP_AUTOPROXY_AUTO_DETECT           0x00000001
+#define WINHTTP_AUTOPROXY_CONFIG_URL            0x00000002
+#define WINHTTP_AUTOPROXY_RUN_INPROCESS         0x00010000
+#define WINHTTP_AUTOPROXY_RUN_OUTPROCESS_ONLY   0x00020000
+#define WINHTTP_AUTO_DETECT_TYPE_DHCP           0x00000001
+#define WINHTTP_AUTO_DETECT_TYPE_DNS_A          0x00000002
+#define WINHTTP_ACCESS_TYPE_DEFAULT_PROXY               0
+#define WINHTTP_ACCESS_TYPE_NO_PROXY                    1
+#define WINHTTP_ACCESS_TYPE_NAMED_PROXY                 3
+#define WINHTTP_NO_PROXY_NAME     NULL
+#define WINHTTP_NO_PROXY_BYPASS   NULL
+
+#endif // _TRY_WINHTTP
+
+#if _TRY_JSPROXY
+extern "C" {
+  typedef BOOL (STDAPICALLTYPE * pfnInternetGetProxyInfo)
+      (
+          LPCSTR lpszUrl,
+          DWORD dwUrlLength,
+          LPSTR lpszUrlHostName,
+          DWORD dwUrlHostNameLength,
+          LPSTR * lplpszProxyHostName,
+          LPDWORD lpdwProxyHostNameLength
+          );
+} // extern "C"
+#endif // _TRY_JSPROXY
+
+#if _TRY_WM_FINDPROXY
+#include <comutil.h>
+#include <wmnetsourcecreator.h>
+#include <wmsinternaladminnetsource.h>
+#endif // _TRY_WM_FINDPROXY
+
+#if _TRY_IE_LAN_SETTINGS
+#include <wininet.h>
+#include <string>
+#endif // _TRY_IE_LAN_SETTINGS
+
+namespace talk_base {
+
+//////////////////////////////////////////////////////////////////////
+// Utility Functions
+//////////////////////////////////////////////////////////////////////
+
+#ifdef WIN32
+#ifdef _UNICODE
+
+typedef std::wstring tstring;
+std::string Utf8String(const tstring& str) { return ToUtf8(str); }
+
+#else  // !_UNICODE
+
+typedef std::string tstring;
+std::string Utf8String(const tstring& str) { return str; }
+
+#endif  // !_UNICODE
+#endif  // WIN32
+
+bool ProxyItemMatch(const Url<char>& url, char * item, size_t len) {
+  // hostname:443
+  if (char * port = ::strchr(item, ':')) {
+    *port++ = '\0';
+    if (url.port() != atol(port)) {
+      return false;
+    }
+  }
+
+  // A.B.C.D or A.B.C.D/24
+  int a, b, c, d, m;
+  int match = sscanf(item, "%d.%d.%d.%d/%d", &a, &b, &c, &d, &m);
+  if (match >= 4) {
+    uint32 ip = ((a & 0xFF) << 24) | ((b & 0xFF) << 16) | ((c & 0xFF) << 8) |
+        (d & 0xFF);
+    if ((match < 5) || (m > 32))
+      m = 32;
+    else if (m < 0)
+      m = 0;
+    uint32 mask = (m == 0) ? 0 : (~0UL) << (32 - m);
+    SocketAddress addr(url.host(), 0);
+    return !addr.IsUnresolved() && ((addr.ip() & mask) == (ip & mask));
+  }
+
+  // .foo.com
+  if (*item == '.') {
+    size_t hostlen = url.host().length();
+    return (hostlen > len)
+        && (stricmp(url.host().c_str() + (hostlen - len), item) == 0);
+  }
+
+  // localhost or www.*.com
+  if (!string_match(url.host().c_str(), item))
+    return false;
+
+  return true;
+}
+
+bool ProxyListMatch(const Url<char>& url, const std::string& proxy_list,
+                    char sep) {
+  const size_t BUFSIZE = 256;
+  char buffer[BUFSIZE];
+  const char* list = proxy_list.c_str();
+  while (*list) {
+    // Remove leading space
+    if (isspace(*list)) {
+      ++list;
+      continue;
+    }
+    // Break on separator
+    size_t len;
+    const char * start = list;
+    if (const char * end = ::strchr(list, sep)) {
+      len = (end - list);
+      list += len + 1;
+    } else {
+      len = strlen(list);
+      list += len;
+    }
+    // Remove trailing space
+    while ((len > 0) && isspace(start[len-1]))
+      --len;
+    // Check for oversized entry
+    if (len >= BUFSIZE)
+      continue;
+    memcpy(buffer, start, len);
+    buffer[len] = 0;
+    if (!ProxyItemMatch(url, buffer, len))
+      continue;
+    return true;
+  }
+  return false;
+}
+
+bool Better(ProxyType lhs, const ProxyType rhs) {
+  // PROXY_NONE, PROXY_HTTPS, PROXY_SOCKS5, PROXY_UNKNOWN
+  const int PROXY_VALUE[5] = { 0, 2, 3, 1 };
+  return (PROXY_VALUE[lhs] > PROXY_VALUE[rhs]);
+}
+
+bool ParseProxy(const std::string& saddress, ProxyInfo* proxy) {
+  const size_t kMaxAddressLength = 1024;
+  // Allow semicolon, space, or tab as an address separator
+  const char* const kAddressSeparator = " ;\t";
+
+  ProxyType ptype;
+  std::string host;
+  uint16 port;
+
+  const char* address = saddress.c_str();
+  while (*address) {
+    size_t len;
+    const char * start = address;
+    if (const char * sep = strchr(address, kAddressSeparator)) {
+      len = (sep - address);
+      address += len + 1;
+      while (*address != '\0' && ::strchr(kAddressSeparator, *address)) {
+        address += 1;
+      }
+    } else {
+      len = strlen(address);
+      address += len;
+    }
+
+    if (len > kMaxAddressLength - 1) {
+      LOG(LS_WARNING) << "Proxy address too long [" << start << "]";
+      continue;
+    }
+
+    char buffer[kMaxAddressLength];
+    memcpy(buffer, start, len);
+    buffer[len] = 0;
+
+    char * colon = ::strchr(buffer, ':');
+    if (!colon) {
+      LOG(LS_WARNING) << "Proxy address without port [" << buffer << "]";
+      continue;
+    }
+
+    *colon = 0;
+    char * endptr;
+    port = static_cast<uint16>(strtol(colon + 1, &endptr, 0));
+    if (*endptr != 0) {
+      LOG(LS_WARNING) << "Proxy address with invalid port [" << buffer << "]";
+      continue;
+    }
+
+    if (char * equals = ::strchr(buffer, '=')) {
+      *equals = 0;
+      host = equals + 1;
+      if (_stricmp(buffer, "socks") == 0) {
+        ptype = PROXY_SOCKS5;
+      } else if (_stricmp(buffer, "https") == 0) {
+        ptype = PROXY_HTTPS;
+      } else {
+        LOG(LS_WARNING) << "Proxy address with unknown protocol ["
+                        << buffer << "]";
+        ptype = PROXY_UNKNOWN;
+      }
+    } else {
+      host = buffer;
+      ptype = PROXY_UNKNOWN;
+    }
+
+    if (Better(ptype, proxy->type)) {
+      proxy->type = ptype;
+      proxy->address.SetIP(host);
+      proxy->address.SetPort(port);
+    }
+  }
+
+  return proxy->type != PROXY_NONE;
+}
+
+UserAgent GetAgent(const char* agent) {
+  if (agent) {
+    std::string agent_str(agent);
+    if (agent_str.find(kFirefoxPattern) != std::string::npos) {
+      return UA_FIREFOX;
+    } else if (agent_str.find(kInternetExplorerPattern) != std::string::npos) {
+      return UA_INTERNETEXPLORER;
+    } else if (agent_str.empty()) {
+      return UA_UNKNOWN;
+    }
+  }
+  return UA_OTHER;
+}
+
+bool EndsWith(const std::string& a, const std::string& b) {
+  if (b.size() > a.size()) {
+    return false;
+  }
+  int result = a.compare(a.size() - b.size(), b.size(), b);
+  return result == 0;
+}
+
+bool GetFirefoxProfilePath(Pathname* path) {
+#ifdef WIN32
+  wchar_t w_path[MAX_PATH];
+  if (SHGetFolderPath(0, CSIDL_APPDATA, 0, SHGFP_TYPE_CURRENT, w_path) !=
+      S_OK) {
+    LOG(LS_ERROR) << "SHGetFolderPath failed";
+    return false;
+  }
+  path->SetFolder(ToUtf8(w_path, wcslen(w_path)));
+  path->AppendFolder("Mozilla");
+  path->AppendFolder("Firefox");
+#elif OSX
+  FSRef fr;
+  if (0 != FSFindFolder(kUserDomain, kApplicationSupportFolderType,
+                        kCreateFolder, &fr)) {
+    LOG(LS_ERROR) << "FSFindFolder failed";
+    return false;
+  }
+  char buffer[NAME_MAX + 1];
+  if (0 != FSRefMakePath(&fr, reinterpret_cast<uint8*>(buffer),
+                         ARRAY_SIZE(buffer))) {
+    LOG(LS_ERROR) << "FSRefMakePath failed";
+    return false;
+  }
+  path->SetFolder(std::string(buffer));
+  path->AppendFolder("Firefox");
+#else
+  char* user_home = getenv("HOME");
+  if (user_home == NULL) {
+    return false;
+  }
+  path->SetFolder(std::string(user_home));
+  path->AppendFolder(".mozilla");
+  path->AppendFolder("firefox");
+#endif  // WIN32
+  return true;
+}
+
+bool GetDefaultFirefoxProfile(Pathname* profile_path) {
+  ASSERT(NULL != profile_path);
+  Pathname path;
+  if (!GetFirefoxProfilePath(&path)) {
+    return false;
+  }
+
+#if USE_FIREFOX_PROFILES_INI
+  // [Profile0]
+  // Name=default
+  // IsRelative=1
+  // Path=Profiles/2de53ejb.default
+  // Default=1
+
+  // Note: we are looking for the first entry with "Default=1", or the last
+  // entry in the file
+  path.SetFilename("profiles.ini");
+  FileStream* fs = Filesystem::OpenFile(path, "r");
+  if (!fs) {
+    return false;
+  }
+  Pathname candidate;
+  bool relative = true;
+  std::string line;
+  while (fs->ReadLine(&line) == SR_SUCCESS) {
+    if (line.length() == 0) {
+      continue;
+    }
+    if (line.at(0) == '[') {
+      relative = true;
+      candidate.clear();
+    } else if (line.find("IsRelative=") == 0 &&
+               line.length() >= 12) {
+      // TODO: The initial Linux public launch revealed a fairly
+      // high number of machines where IsRelative= did not have anything after
+      // it. Perhaps that is legal profiles.ini syntax?
+      relative = (line.at(11) != '0');
+    } else if (line.find("Path=") == 0 &&
+               line.length() >= 6) {
+      if (relative) {
+        candidate = path;
+      } else {
+        candidate.clear();
+      }
+      candidate.AppendFolder(line.substr(5));
+    } else if (line.find("Default=") == 0 &&
+               line.length() >= 9) {
+      if ((line.at(8) != '0') && !candidate.empty()) {
+        break;
+      }
+    }
+  }
+  fs->Close();
+  if (candidate.empty()) {
+    return false;
+  }
+  profile_path->SetPathname(candidate.pathname());
+
+#else // !USE_FIREFOX_PROFILES_INI
+  path.AppendFolder("Profiles");
+  DirectoryIterator* it = Filesystem::IterateDirectory();
+  it->Iterate(path);
+  std::string extension(".default");
+  while (!EndsWith(it->Name(), extension)) {
+    if (!it->Next()) {
+      return false;
+    }
+  }
+
+  profile_path->SetPathname(path);
+  profile->AppendFolder("Profiles");
+  profile->AppendFolder(it->Name());
+  delete it;
+
+#endif // !USE_FIREFOX_PROFILES_INI
+
+  return true;
+}
+
+bool ReadFirefoxPrefs(const Pathname& filename,
+                      const char * prefix,
+                      StringMap* settings) {
+  FileStream* fs = Filesystem::OpenFile(filename, "r");
+  if (!fs) {
+    LOG(LS_ERROR) << "Failed to open file: " << filename.pathname();
+    return false;
+  }
+
+  std::string line;
+  while (fs->ReadLine(&line) == SR_SUCCESS) {
+    size_t prefix_len = strlen(prefix);
+
+    // Skip blank lines and too long lines.
+    if ((line.length() == 0) || (line.length() > kMaxLineLength)
+        || (line.at(0) == '#') || line.compare(0, 2, "/*") == 0
+        || line.compare(0, 2, " *") == 0) {
+      continue;
+    }
+
+    char buffer[kMaxLineLength];
+    strcpyn(buffer, sizeof(buffer), line.c_str());
+    int nstart = 0, nend = 0, vstart = 0, vend = 0;
+    sscanf(buffer, "user_pref(\"%n%*[^\"]%n\", %n%*[^)]%n);",
+           &nstart, &nend, &vstart, &vend);
+    if (vend > 0) {
+      char* name = buffer + nstart;
+      name[nend - nstart] = 0;
+      if ((vend - vstart >= 2) && (buffer[vstart] == '"')) {
+        vstart += 1;
+        vend -= 1;
+      }
+      char* value = buffer + vstart;
+      value[vend - vstart] = 0;
+      if ((strncmp(name, prefix, prefix_len) == 0) && *value) {
+        settings->Add(name + prefix_len, value);
+      }
+    } else {
+      LOG_F(LS_WARNING) << "Unparsed pref [" << buffer << "]";
+    }
+  }
+  fs->Close();
+  return true;
+}
+
+bool GetFirefoxProxySettings(const char* url, ProxyInfo* proxy) {
+  Url<char> purl(url);
+  Pathname path;
+  bool success = false;
+  if (GetDefaultFirefoxProfile(&path)) {
+    StringMap settings;
+    path.SetFilename("prefs.js");
+    if (ReadFirefoxPrefs(path, "network.proxy.", &settings)) {
+      success = true;
+      proxy->bypass_list =
+          settings.Get("no_proxies_on", "localhost, 127.0.0.1");
+      if (settings.Get("type") == "1") {
+        // User has manually specified a proxy, try to figure out what
+        // type it is.
+        if (ProxyListMatch(purl, proxy->bypass_list.c_str(), ',')) {
+          // Our url is in the list of url's to bypass proxy.
+        } else if (settings.Get("share_proxy_settings") == "true") {
+          proxy->type = PROXY_UNKNOWN;
+          proxy->address.SetIP(settings.Get("http"));
+          proxy->address.SetPort(atoi(settings.Get("http_port").c_str()));
+        } else if (settings.IsSet("socks")) {
+          proxy->type = PROXY_SOCKS5;
+          proxy->address.SetIP(settings.Get("socks"));
+          proxy->address.SetPort(atoi(settings.Get("socks_port").c_str()));
+        } else if (settings.IsSet("ssl")) {
+          proxy->type = PROXY_HTTPS;
+          proxy->address.SetIP(settings.Get("ssl"));
+          proxy->address.SetPort(atoi(settings.Get("ssl_port").c_str()));
+        } else if (settings.IsSet("http")) {
+          proxy->type = PROXY_HTTPS;
+          proxy->address.SetIP(settings.Get("http"));
+          proxy->address.SetPort(atoi(settings.Get("http_port").c_str()));
+        }
+      } else if (settings.Get("type") == "2") {
+        // Browser is configured to get proxy settings from a given url.
+        proxy->autoconfig_url = settings.Get("autoconfig_url").c_str();
+      } else if (settings.Get("type") == "4") {
+        // Browser is configured to auto detect proxy config.
+        proxy->autodetect = true;
+      } else {
+        // No proxy set.
+      }
+    }
+  }
+  return success;
+}
+
+#ifdef WIN32  // Windows specific implementation for reading Internet
+              // Explorer proxy settings.
+
+void LogGetProxyFault() {
+  LOG_GLEM(LERROR, WINHTTP) << "WinHttpGetProxyForUrl faulted!!";
+}
+
+BOOL MyWinHttpGetProxyForUrl(pfnWinHttpGetProxyForUrl pWHGPFU,
+                             HINTERNET hWinHttp, LPCWSTR url,
+                             WINHTTP_AUTOPROXY_OPTIONS *options,
+                             WINHTTP_PROXY_INFO *info) {
+  // WinHttpGetProxyForUrl() can call plugins which can crash.
+  // In the case of McAfee scriptproxy.dll, it does crash in
+  // older versions. Try to catch crashes here and treat as an
+  // error.
+  BOOL success = FALSE;
+
+#if (_HAS_EXCEPTIONS == 0)
+  __try {
+    success = pWHGPFU(hWinHttp, url, options, info);
+  } __except(EXCEPTION_EXECUTE_HANDLER) {
+    // This is a separate function to avoid
+    // Visual C++ error 2712 when compiling with C++ EH
+    LogGetProxyFault();
+  }
+#else
+  success = pWHGPFU(hWinHttp, url, options, info);
+#endif  // (_HAS_EXCEPTIONS == 0)
+
+  return success;
+}
+
+bool IsDefaultBrowserFirefox() {
+  HKEY key;
+  LONG result = RegOpenKeyEx(HKEY_CLASSES_ROOT, L"http\\shell\\open\\command",
+                             0, KEY_READ, &key);
+  if (ERROR_SUCCESS != result)
+    return false;
+
+  wchar_t* value = NULL;
+  DWORD size, type;
+  result = RegQueryValueEx(key, L"", 0, &type, NULL, &size);
+  if (REG_SZ != type) {
+    result = ERROR_ACCESS_DENIED;  // Any error is fine
+  } else if (ERROR_SUCCESS == result) {
+    value = new wchar_t[size+1];
+    BYTE* buffer = reinterpret_cast<BYTE*>(value);
+    result = RegQueryValueEx(key, L"", 0, &type, buffer, &size);
+  }
+  RegCloseKey(key);
+
+  bool success = false;
+  if (ERROR_SUCCESS == result) {
+    value[size] = L'\0';
+    for (size_t i = 0; i < size; ++i) {
+      value[i] = tolowercase(value[i]);
+    }
+    success = (NULL != strstr(value, L"firefox.exe"));
+  }
+  delete [] value;
+  return success;
+}
+
+bool GetWinHttpProxySettings(const char* url, ProxyInfo* proxy) {
+  HMODULE winhttp_handle = LoadLibrary(L"winhttp.dll");
+  if (winhttp_handle == NULL) {
+    LOG(LS_ERROR) << "Failed to load winhttp.dll.";
+    return false;
+  }
+  WINHTTP_CURRENT_USER_IE_PROXY_CONFIG iecfg;
+  memset(&iecfg, 0, sizeof(iecfg));
+  Url<char> purl(url);
+  pfnWinHttpGetIEProxyConfig pWHGIEPC =
+      reinterpret_cast<pfnWinHttpGetIEProxyConfig>(
+          GetProcAddress(winhttp_handle,
+                         "WinHttpGetIEProxyConfigForCurrentUser"));
+  bool success = false;
+  if (pWHGIEPC && pWHGIEPC(&iecfg)) {
+    // We were read proxy config successfully.
+    success = true;
+    if (iecfg.fAutoDetect) {
+      proxy->autodetect = true;
+    }
+    if (iecfg.lpszAutoConfigUrl) {
+      proxy->autoconfig_url = ToUtf8(iecfg.lpszAutoConfigUrl);
+      GlobalFree(iecfg.lpszAutoConfigUrl);
+    }
+    if (iecfg.lpszProxyBypass) {
+      proxy->bypass_list = ToUtf8(iecfg.lpszProxyBypass);
+      GlobalFree(iecfg.lpszProxyBypass);
+    }
+    if (iecfg.lpszProxy) {
+      if (!ProxyListMatch(purl, proxy->bypass_list, ';')) {
+        ParseProxy(ToUtf8(iecfg.lpszProxy), proxy);
+      }
+      GlobalFree(iecfg.lpszProxy);
+    }
+  }
+  FreeLibrary(winhttp_handle);
+  return success;
+}
+
+// Uses the WinHTTP API to auto detect proxy for the given url. Firefox and IE
+// have slightly different option dialogs for proxy settings. In Firefox,
+// either a location of a proxy configuration file can be specified or auto
+// detection can be selected. In IE theese two options can be independently
+// selected. For the case where both options are selected (only IE) we try to
+// fetch the config file first, and if that fails we'll perform an auto
+// detection.
+//
+// Returns true if we successfully performed an auto detection not depending on
+// whether we found a proxy or not. Returns false on error.
+bool WinHttpAutoDetectProxyForUrl(const char* agent, const char* url,
+                                  ProxyInfo* proxy) {
+  Url<char> purl(url);
+  bool success = true;
+  HMODULE winhttp_handle = LoadLibrary(L"winhttp.dll");
+  if (winhttp_handle == NULL) {
+    LOG(LS_ERROR) << "Failed to load winhttp.dll.";
+    return false;
+  }
+  pfnWinHttpOpen pWHO =
+      reinterpret_cast<pfnWinHttpOpen>(GetProcAddress(winhttp_handle,
+                                                      "WinHttpOpen"));
+  pfnWinHttpCloseHandle pWHCH =
+      reinterpret_cast<pfnWinHttpCloseHandle>(
+          GetProcAddress(winhttp_handle, "WinHttpCloseHandle"));
+  pfnWinHttpGetProxyForUrl pWHGPFU =
+      reinterpret_cast<pfnWinHttpGetProxyForUrl>(
+          GetProcAddress(winhttp_handle, "WinHttpGetProxyForUrl"));
+  if (pWHO && pWHCH && pWHGPFU) {
+    if (HINTERNET hWinHttp = pWHO(ToUtf16(agent).c_str(),
+                                  WINHTTP_ACCESS_TYPE_NO_PROXY,
+                                  WINHTTP_NO_PROXY_NAME,
+                                  WINHTTP_NO_PROXY_BYPASS,
+                                  0)) {
+      BOOL result = FALSE;
+      WINHTTP_PROXY_INFO info;
+      memset(&info, 0, sizeof(info));
+      if (proxy->autodetect) {
+        // Use DHCP and DNS to try to find any proxy to use.
+        WINHTTP_AUTOPROXY_OPTIONS options;
+        memset(&options, 0, sizeof(options));
+        options.fAutoLogonIfChallenged = TRUE;
+
+        options.dwFlags |= WINHTTP_AUTOPROXY_AUTO_DETECT;
+        options.dwAutoDetectFlags |= WINHTTP_AUTO_DETECT_TYPE_DHCP
+            | WINHTTP_AUTO_DETECT_TYPE_DNS_A;
+        result = MyWinHttpGetProxyForUrl(
+            pWHGPFU, hWinHttp, ToUtf16(url).c_str(), &options, &info);
+      }
+      if (!result && !proxy->autoconfig_url.empty()) {
+        // We have the location of a proxy config file. Download it and
+        // execute it to find proxy settings for our url.
+        WINHTTP_AUTOPROXY_OPTIONS options;
+        memset(&options, 0, sizeof(options));
+        memset(&info, 0, sizeof(info));
+        options.fAutoLogonIfChallenged = TRUE;
+
+        std::wstring autoconfig_url16((ToUtf16)(proxy->autoconfig_url));
+        options.dwFlags |= WINHTTP_AUTOPROXY_CONFIG_URL;
+        options.lpszAutoConfigUrl = autoconfig_url16.c_str();
+
+        result = MyWinHttpGetProxyForUrl(
+            pWHGPFU, hWinHttp, ToUtf16(url).c_str(), &options, &info);
+      }
+      if (result) {
+        // Either the given auto config url was valid or auto
+        // detection found a proxy on this network.
+        if (info.lpszProxy) {
+          // TODO: Does this bypass list differ from the list
+          // retreived from GetWinHttpProxySettings earlier?
+          if (info.lpszProxyBypass) {
+            proxy->bypass_list = ToUtf8(info.lpszProxyBypass);
+            GlobalFree(info.lpszProxyBypass);
+          } else {
+            proxy->bypass_list.clear();
+          }
+          if (!ProxyListMatch(purl, proxy->bypass_list, ';')) {
+            // Found proxy for this URL. If parsing the address turns
+            // out ok then we are successful.
+            success = ParseProxy(ToUtf8(info.lpszProxy), proxy);
+          }
+          GlobalFree(info.lpszProxy);
+        }
+      } else {
+        // We could not find any proxy for this url.
+        LOG(LS_INFO) << "No proxy detected for " << url;
+      }
+      pWHCH(hWinHttp);
+    }
+  } else {
+    LOG(LS_ERROR) << "Failed loading WinHTTP functions.";
+    success = false;
+  }
+  FreeLibrary(winhttp_handle);
+  return success;
+}
+
+#if 0  // Below functions currently not used.
+
+bool GetJsProxySettings(const char* url, ProxyInfo* proxy) {
+  Url<char> purl(url);
+  bool success = false;
+
+  if (HMODULE hModJS = LoadLibrary(_T("jsproxy.dll"))) {
+    pfnInternetGetProxyInfo pIGPI =
+        reinterpret_cast<pfnInternetGetProxyInfo>(
+            GetProcAddress(hModJS, "InternetGetProxyInfo"));
+    if (pIGPI) {
+      char proxy[256], host[256];
+      memset(proxy, 0, sizeof(proxy));
+      char * ptr = proxy;
+      DWORD proxylen = sizeof(proxy);
+      std::string surl = Utf8String(url);
+      DWORD hostlen = _snprintf(host, sizeof(host), "http%s://%S",
+                                purl.secure() ? "s" : "", purl.server());
+      if (pIGPI(surl.data(), surl.size(), host, hostlen, &ptr, &proxylen)) {
+        LOG(INFO) << "Proxy: " << proxy;
+      } else {
+        LOG_GLE(INFO) << "InternetGetProxyInfo";
+      }
+    }
+    FreeLibrary(hModJS);
+  }
+  return success;
+}
+
+bool GetWmProxySettings(const char* url, ProxyInfo* proxy) {
+  Url<char> purl(url);
+  bool success = false;
+
+  INSNetSourceCreator * nsc = 0;
+  HRESULT hr = CoCreateInstance(CLSID_ClientNetManager, 0, CLSCTX_ALL,
+                                IID_INSNetSourceCreator, (LPVOID *) &nsc);
+  if (SUCCEEDED(hr)) {
+    if (SUCCEEDED(hr = nsc->Initialize())) {
+      VARIANT dispatch;
+      VariantInit(&dispatch);
+      if (SUCCEEDED(hr = nsc->GetNetSourceAdminInterface(L"http", &dispatch))) {
+        IWMSInternalAdminNetSource * ians = 0;
+        if (SUCCEEDED(hr = dispatch.pdispVal->QueryInterface(
+                IID_IWMSInternalAdminNetSource, (LPVOID *) &ians))) {
+          _bstr_t host(purl.server());
+          BSTR proxy = 0;
+          BOOL bProxyEnabled = FALSE;
+          DWORD port, context = 0;
+          if (SUCCEEDED(hr = ians->FindProxyForURL(
+                  L"http", host, &bProxyEnabled, &proxy, &port, &context))) {
+            success = true;
+            if (bProxyEnabled) {
+              _bstr_t sproxy = proxy;
+              proxy->ptype = PT_HTTPS;
+              proxy->host = sproxy;
+              proxy->port = port;
+            }
+          }
+          SysFreeString(proxy);
+          if (FAILED(hr = ians->ShutdownProxyContext(context))) {
+            LOG(LS_INFO) << "IWMSInternalAdminNetSource::ShutdownProxyContext"
+                         << "failed: " << hr;
+          }
+          ians->Release();
+        }
+      }
+      VariantClear(&dispatch);
+      if (FAILED(hr = nsc->Shutdown())) {
+        LOG(LS_INFO) << "INSNetSourceCreator::Shutdown failed: " << hr;
+      }
+    }
+    nsc->Release();
+  }
+  return success;
+}
+
+bool GetIePerConnectionProxySettings(const char* url, ProxyInfo* proxy) {
+  Url<char> purl(url);
+  bool success = false;
+
+  INTERNET_PER_CONN_OPTION_LIST list;
+  INTERNET_PER_CONN_OPTION options[3];
+  memset(&list, 0, sizeof(list));
+  memset(&options, 0, sizeof(options));
+
+  list.dwSize = sizeof(list);
+  list.dwOptionCount = 3;
+  list.pOptions = options;
+  options[0].dwOption = INTERNET_PER_CONN_FLAGS;
+  options[1].dwOption = INTERNET_PER_CONN_PROXY_SERVER;
+  options[2].dwOption = INTERNET_PER_CONN_PROXY_BYPASS;
+  DWORD dwSize = sizeof(list);
+
+  if (!InternetQueryOption(0, INTERNET_OPTION_PER_CONNECTION_OPTION, &list,
+                           &dwSize)) {
+    LOG(LS_INFO) << "InternetQueryOption failed: " << GetLastError();
+  } else if ((options[0].Value.dwValue & PROXY_TYPE_PROXY) != 0) {
+    success = true;
+    if (!ProxyListMatch(purl, nonnull(options[2].Value.pszValue), _T(';'))) {
+      ParseProxy(nonnull(options[1].Value.pszValue), proxy);
+    }
+  } else if ((options[0].Value.dwValue & PROXY_TYPE_DIRECT) != 0) {
+    success = true;
+  } else {
+    LOG(LS_INFO) << "unknown internet access type: "
+                 << options[0].Value.dwValue;
+  }
+  if (options[1].Value.pszValue) {
+    GlobalFree(options[1].Value.pszValue);
+  }
+  if (options[2].Value.pszValue) {
+    GlobalFree(options[2].Value.pszValue);
+  }
+  return success;
+}
+
+#endif  // 0
+
+// Uses the InternetQueryOption function to retrieve proxy settings
+// from the registry. This will only give us the 'static' settings,
+// ie, not any information about auto config etc.
+bool GetIeLanProxySettings(const char* url, ProxyInfo* proxy) {
+  Url<char> purl(url);
+  bool success = false;
+
+  wchar_t buffer[1024];
+  memset(buffer, 0, sizeof(buffer));
+  INTERNET_PROXY_INFO * info = reinterpret_cast<INTERNET_PROXY_INFO *>(buffer);
+  DWORD dwSize = sizeof(buffer);
+
+  if (!InternetQueryOption(0, INTERNET_OPTION_PROXY, info, &dwSize)) {
+    LOG(LS_INFO) << "InternetQueryOption failed: " << GetLastError();
+  } else if (info->dwAccessType == INTERNET_OPEN_TYPE_DIRECT) {
+    success = true;
+  } else if (info->dwAccessType == INTERNET_OPEN_TYPE_PROXY) {
+    success = true;
+    if (!ProxyListMatch(purl, nonnull(reinterpret_cast<const char*>(
+            info->lpszProxyBypass)), ' ')) {
+      ParseProxy(nonnull(reinterpret_cast<const char*>(info->lpszProxy)),
+                 proxy);
+    }
+  } else {
+    LOG(LS_INFO) << "unknown internet access type: " << info->dwAccessType;
+  }
+  return success;
+}
+
+bool GetIeProxySettings(const char* agent, const char* url, ProxyInfo* proxy) {
+  bool success = GetWinHttpProxySettings(url, proxy);
+  if (!success) {
+    // TODO: Should always call this if no proxy were detected by
+    // GetWinHttpProxySettings?
+    // WinHttp failed. Try using the InternetOptionQuery method instead.
+    return GetIeLanProxySettings(url, proxy);
+  }
+  return true;
+}
+
+#endif  // WIN32
+
+#ifdef OSX  // OSX specific implementation for reading system wide
+            // proxy settings.
+
+bool p_getProxyInfoForTypeFromDictWithKeys(ProxyInfo* proxy,
+                                           ProxyType type,
+                                           const CFDictionaryRef proxyDict,
+                                           const CFStringRef enabledKey,
+                                           const CFStringRef hostKey,
+                                           const CFStringRef portKey) {
+  // whether or not we set up the proxy info.
+  bool result = false;
+
+  // we use this as a scratch variable for determining if operations
+  // succeeded.
+  bool converted = false;
+
+  // the data we need to construct the SocketAddress for the proxy.
+  std::string hostname;
+  int port;
+
+  if ((proxyDict != NULL) &&
+      (CFGetTypeID(proxyDict) == CFDictionaryGetTypeID())) {
+    // CoreFoundation stuff that we'll have to get from
+    // the dictionaries and interpret or convert into more usable formats.
+    CFNumberRef enabledCFNum;
+    CFNumberRef portCFNum;
+    CFStringRef hostCFStr;
+
+    enabledCFNum = (CFNumberRef)CFDictionaryGetValue(proxyDict, enabledKey);
+
+    if (p_isCFNumberTrue(enabledCFNum)) {
+      // let's see if we can get the address and port.
+      hostCFStr = (CFStringRef)CFDictionaryGetValue(proxyDict, hostKey);
+      converted = p_convertHostCFStringRefToCPPString(hostCFStr, hostname);
+      if (converted) {
+        portCFNum = (CFNumberRef)CFDictionaryGetValue(proxyDict, portKey);
+        converted = p_convertCFNumberToInt(portCFNum, &port);
+        if (converted) {
+          // we have something enabled, with a hostname and a port.
+          // That's sufficient to set up the proxy info.
+          proxy->type = type;
+          proxy->address.SetIP(hostname);
+          proxy->address.SetPort(port);
+          result = true;
+        }
+      }
+    }
+  }
+
+  return result;
+}
+
+// Looks for proxy information in the given dictionary,
+// return true if it found sufficient information to define one,
+// false otherwise.  This is guaranteed to not change the values in proxy
+// unless a full-fledged proxy description was discovered in the dictionary.
+// However, at the present time this does not support username or password.
+// Checks first for a SOCKS proxy, then for HTTPS, then HTTP.
+bool GetMacProxySettingsFromDictionary(ProxyInfo* proxy,
+                                       const CFDictionaryRef proxyDict) {
+  // the function result.
+  bool gotProxy = false;
+
+
+  // first we see if there's a SOCKS proxy in place.
+  gotProxy = p_getProxyInfoForTypeFromDictWithKeys(proxy,
+                                                   PROXY_SOCKS5,
+                                                   proxyDict,
+                                                   kSCPropNetProxiesSOCKSEnable,
+                                                   kSCPropNetProxiesSOCKSProxy,
+                                                   kSCPropNetProxiesSOCKSPort);
+
+  if (!gotProxy) {
+    // okay, no SOCKS proxy, let's look for https.
+    gotProxy = p_getProxyInfoForTypeFromDictWithKeys(proxy,
+                                               PROXY_HTTPS,
+                                               proxyDict,
+                                               kSCPropNetProxiesHTTPSEnable,
+                                               kSCPropNetProxiesHTTPSProxy,
+                                               kSCPropNetProxiesHTTPSPort);
+    if (!gotProxy) {
+      // Finally, try HTTP proxy. Note that flute doesn't
+      // differentiate between HTTPS and HTTP, hence we are using the
+      // same flute type here, ie. PROXY_HTTPS.
+      gotProxy = p_getProxyInfoForTypeFromDictWithKeys(
+          proxy, PROXY_HTTPS, proxyDict, kSCPropNetProxiesHTTPEnable,
+          kSCPropNetProxiesHTTPProxy, kSCPropNetProxiesHTTPPort);
+    }
+  }
+  return gotProxy;
+}
+
+bool p_putPasswordInProxyInfo(ProxyInfo* proxy) {
+  bool result = true;  // by default we assume we're good.
+  // for all we know there isn't any password.  We'll set to false
+  // if we find a problem.
+
+  // Ask the keychain for an internet password search for the given protocol.
+  OSStatus oss = 0;
+  SecKeychainAttributeList attrList;
+  attrList.count = 3;
+  SecKeychainAttribute attributes[3];
+  attrList.attr = attributes;
+
+  attributes[0].tag = kSecProtocolItemAttr;
+  attributes[0].length = sizeof(SecProtocolType);
+  SecProtocolType protocol;
+  switch (proxy->type) {
+    case PROXY_HTTPS :
+      protocol = kSecProtocolTypeHTTPS;
+      break;
+    case PROXY_SOCKS5 :
+      protocol = kSecProtocolTypeSOCKS;
+      break;
+    default :
+      LOG(LS_ERROR) << "asked for proxy password for unknown proxy type.";
+      result = false;
+      break;
+  }
+  attributes[0].data = &protocol;
+
+  UInt32 port = proxy->address.port();
+  attributes[1].tag = kSecPortItemAttr;
+  attributes[1].length = sizeof(UInt32);
+  attributes[1].data = &port;
+
+  std::string ip = proxy->address.IPAsString();
+  attributes[2].tag = kSecServerItemAttr;
+  attributes[2].length = ip.length();
+  attributes[2].data = const_cast<char*>(ip.c_str());
+
+  if (result) {
+    LOG(LS_INFO) << "trying to get proxy username/password";
+    SecKeychainSearchRef sref;
+    oss = SecKeychainSearchCreateFromAttributes(NULL,
+                                                kSecInternetPasswordItemClass,
+                                                &attrList, &sref);
+    if (0 == oss) {
+      LOG(LS_INFO) << "SecKeychainSearchCreateFromAttributes was good";
+      // Get the first item, if there is one.
+      SecKeychainItemRef iref;
+      oss = SecKeychainSearchCopyNext(sref, &iref);
+      if (0 == oss) {
+        LOG(LS_INFO) << "...looks like we have the username/password data";
+        // If there is, get the username and the password.
+
+        SecKeychainAttributeInfo attribsToGet;
+        attribsToGet.count = 1;
+        UInt32 tag = kSecAccountItemAttr;
+        UInt32 format = CSSM_DB_ATTRIBUTE_FORMAT_STRING;
+        void *data;
+        UInt32 length;
+        SecKeychainAttributeList *localList;
+
+        attribsToGet.tag = &tag;
+        attribsToGet.format = &format;
+        OSStatus copyres = SecKeychainItemCopyAttributesAndData(iref,
+                                                                &attribsToGet,
+                                                                NULL,
+                                                                &localList,
+                                                                &length,
+                                                                &data);
+        if (0 == copyres) {
+          LOG(LS_INFO) << "...and we can pull it out.";
+          // now, we know from experimentation (sadly not from docs)
+          // that the username is in the local attribute list,
+          // and the password in the data,
+          // both without null termination but with info on their length.
+          // grab the password from the data.
+          std::string password;
+          password.append(static_cast<const char*>(data), length);
+
+          // make the password into a CryptString
+          // huh, at the time of writing, you can't.
+          // so we'll skip that for now and come back to it later.
+
+          // now put the username in the proxy.
+          if (1 <= localList->attr->length) {
+            proxy->username.append(
+                static_cast<const char*>(localList->attr->data),
+                localList->attr->length);
+            LOG(LS_INFO) << "username is " << proxy->username;
+          } else {
+            LOG(LS_ERROR) << "got keychain entry with no username";
+            result = false;
+          }
+        } else {
+          LOG(LS_ERROR) << "couldn't copy info from keychain.";
+          result = false;
+        }
+        SecKeychainItemFreeAttributesAndData(localList, data);
+      } else if (errSecItemNotFound == oss) {
+        LOG(LS_INFO) << "...username/password info not found";
+      } else {
+        // oooh, neither 0 nor itemNotFound.
+        LOG(LS_ERROR) << "Couldn't get keychain information, error code" << oss;
+        result = false;
+      }
+    } else if (errSecItemNotFound == oss) {  // noop
+    } else {
+      // oooh, neither 0 nor itemNotFound.
+      LOG(LS_ERROR) << "Couldn't get keychain information, error code" << oss;
+      result = false;
+    }
+  }
+
+  return result;
+}
+
+bool GetMacProxySettings(ProxyInfo* proxy) {
+  // based on the Apple Technical Q&A QA1234
+  // http://developer.apple.com/qa/qa2001/qa1234.html
+  CFDictionaryRef proxyDict = SCDynamicStoreCopyProxies(NULL);
+  bool result = false;
+
+  if (proxyDict != NULL) {
+    // sending it off to another function makes it easier to unit test
+    // since we can make our own dictionary to hand to that function.
+    result = GetMacProxySettingsFromDictionary(proxy, proxyDict);
+
+    if (result) {
+      result = p_putPasswordInProxyInfo(proxy);
+    }
+
+    // We created the dictionary with something that had the
+    // word 'copy' in it, so we have to release it, according
+    // to the Carbon memory management standards.
+    CFRelease(proxyDict);
+  } else {
+    LOG(LS_ERROR) << "SCDynamicStoreCopyProxies failed";
+  }
+
+  return result;
+}
+#endif  // OSX
+
+bool AutoDetectProxySettings(const char* agent, const char* url,
+                             ProxyInfo* proxy) {
+#ifdef WIN32
+  return WinHttpAutoDetectProxyForUrl(agent, url, proxy);
+#else
+  LOG(LS_WARNING) << "Proxy auto-detection not implemented for this platform";
+  return false;
+#endif
+}
+
+bool GetSystemDefaultProxySettings(const char* agent, const char* url,
+                                   ProxyInfo* proxy) {
+#ifdef WIN32
+  return GetIeProxySettings(agent, url, proxy);
+#elif OSX
+  return GetMacProxySettings(proxy);
+#else
+  // TODO: Get System settings if browser is not firefox.
+  return GetFirefoxProxySettings(url, proxy);
+#endif
+}
+
+bool GetProxySettingsForUrl(const char* agent, const char* url,
+                            ProxyInfo& proxy, bool long_operation) {
+  UserAgent a = GetAgent(agent);
+  bool result;
+  switch (a) {
+    case UA_FIREFOX: {
+      result = GetFirefoxProxySettings(url, &proxy);
+      break;
+    }
+#ifdef WIN32
+    case UA_INTERNETEXPLORER:
+      result = GetIeProxySettings(agent, url, &proxy);
+      break;
+    case UA_UNKNOWN:
+      // Agent not defined, check default browser.
+      if (IsDefaultBrowserFirefox()) {
+        result = GetFirefoxProxySettings(url, &proxy);
+      } else {
+        result = GetIeProxySettings(agent, url, &proxy);
+      }
+      break;
+#endif  // WIN32
+    default:
+      result = GetSystemDefaultProxySettings(agent, url, &proxy);
+      break;
+  }
+
+  // TODO: Consider using the 'long_operation' parameter to
+  // decide whether to do the auto detection.
+  if (result && (proxy.autodetect ||
+                 !proxy.autoconfig_url.empty())) {
+    // Use WinHTTP to auto detect proxy for us.
+    result = AutoDetectProxySettings(agent, url, &proxy);
+    if (!result) {
+      // Either auto detection is not supported or we simply didn't
+      // find any proxy, reset type.
+      proxy.type = talk_base::PROXY_NONE;
+    }
+  }
+  return result;
+}
+
+}  // namespace talk_base
diff --git a/talk/base/proxydetect.h b/talk/base/proxydetect.h
new file mode 100644
index 0000000..36ee672
--- /dev/null
+++ b/talk/base/proxydetect.h
@@ -0,0 +1,48 @@
+/*
+ * libjingle
+ * Copyright 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.
+ */
+
+#ifndef _PROXYDETECT_H_
+#define _PROXYDETECT_H_
+
+#include "talk/base/proxyinfo.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+namespace talk_base {
+// Auto-detect the proxy server.  Returns true if a proxy is configured,
+// although hostname may be empty if the proxy is not required for
+// the given URL.
+
+bool GetProxySettingsForUrl(const char* agent, const char* url,
+                            talk_base::ProxyInfo& proxy,
+                            bool long_operation = false);
+
+}  // namespace talk_base
+
+#endif  // _PROXYDETECT_H_
diff --git a/talk/base/proxyinfo.cc b/talk/base/proxyinfo.cc
new file mode 100644
index 0000000..1d9c588
--- /dev/null
+++ b/talk/base/proxyinfo.cc
@@ -0,0 +1,37 @@
+/*
+ * 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/base/proxyinfo.h"
+
+namespace talk_base {
+
+const char * ProxyToString(ProxyType proxy) {
+  const char * const PROXY_NAMES[] = { "none", "https", "socks5", "unknown" };
+  return PROXY_NAMES[proxy];
+}
+
+} // namespace talk_base
diff --git a/talk/base/proxyinfo.h b/talk/base/proxyinfo.h
new file mode 100644
index 0000000..9e28f1a
--- /dev/null
+++ b/talk/base/proxyinfo.h
@@ -0,0 +1,59 @@
+/*
+ * 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_BASE_PROXYINFO_H__
+#define TALK_BASE_PROXYINFO_H__
+
+#include <string>
+#include "talk/base/socketaddress.h"
+#include "talk/base/cryptstring.h"
+
+namespace talk_base {
+
+enum ProxyType {
+  PROXY_NONE,
+  PROXY_HTTPS,
+  PROXY_SOCKS5,
+  PROXY_UNKNOWN
+};
+const char * ProxyToString(ProxyType proxy);
+
+struct ProxyInfo {
+  ProxyType type;
+  SocketAddress address;
+  std::string autoconfig_url;
+  bool autodetect;
+  std::string bypass_list;
+  std::string username;
+  CryptString password;
+
+  ProxyInfo() : type(PROXY_NONE), autodetect(false) { }
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_PROXYINFO_H__
diff --git a/talk/base/ratetracker.cc b/talk/base/ratetracker.cc
new file mode 100644
index 0000000..d5207cd
--- /dev/null
+++ b/talk/base/ratetracker.cc
@@ -0,0 +1,80 @@
+/*
+ * libjingle
+ * Copyright 2004--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 "talk/base/ratetracker.h"
+#include "talk/base/time.h"
+
+namespace talk_base {
+
+RateTracker::RateTracker()
+    : total_units_(0), units_second_(0),
+      last_units_second_time_(static_cast<uint32>(-1)),
+      last_units_second_calc_(0) {
+}
+
+size_t RateTracker::total_units() const {
+  return total_units_;
+}
+
+size_t RateTracker::units_second() {
+  // Snapshot units / second calculator. Determine how many seconds have
+  // elapsed since our last reference point. If over 1 second, establish
+  // a new reference point that is an integer number of seconds since the
+  // last one, and compute the units over that interval.
+  uint32 current_time = Time();
+  if (last_units_second_time_ != static_cast<uint32>(-1)) {
+    int delta = talk_base::TimeDiff(current_time, last_units_second_time_);
+    if (delta >= 1000) {
+      int fraction_time = delta % 1000;
+      int seconds = delta / 1000;
+      int fraction_units =
+          static_cast<int>(total_units_ - last_units_second_calc_) *
+              fraction_time / delta;
+      // Compute "units received during the interval" / "seconds in interval"
+      units_second_ =
+          (total_units_ - last_units_second_calc_ - fraction_units) / seconds;
+      last_units_second_time_ = current_time - fraction_time;
+      last_units_second_calc_ = total_units_ - fraction_units;
+    }
+  }
+  if (last_units_second_time_ == static_cast<uint32>(-1)) {
+    last_units_second_time_ = current_time;
+    last_units_second_calc_ = total_units_;
+  }
+
+  return units_second_;
+}
+
+void RateTracker::Update(size_t units) {
+  total_units_ += units;
+}
+
+uint32 RateTracker::Time() const {
+  return talk_base::Time();
+}
+
+}  // namespace talk_base
diff --git a/talk/base/ratetracker.h b/talk/base/ratetracker.h
new file mode 100644
index 0000000..28c7bb3
--- /dev/null
+++ b/talk/base/ratetracker.h
@@ -0,0 +1,59 @@
+/*
+ * libjingle
+ * Copyright 2004--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.
+ */
+
+#ifndef TALK_BASE_RATETRACKER_H_
+#define TALK_BASE_RATETRACKER_H_
+
+#include <stdlib.h>
+#include "talk/base/basictypes.h"
+
+namespace talk_base {
+
+// Computes instantaneous units per second.
+class RateTracker {
+ public:
+  RateTracker();
+  virtual ~RateTracker() {}
+
+  size_t total_units() const;
+  size_t units_second();
+  void Update(size_t units);
+
+ protected:
+  // overrideable for tests
+  virtual uint32 Time() const;
+
+ private:
+  size_t total_units_;
+  size_t units_second_;
+  uint32 last_units_second_time_;
+  size_t last_units_second_calc_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_RATETRACKER_H_
diff --git a/talk/base/schanneladapter.cc b/talk/base/schanneladapter.cc
new file mode 100644
index 0000000..01aa9ce
--- /dev/null
+++ b/talk/base/schanneladapter.cc
@@ -0,0 +1,722 @@
+/*
+ * 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/base/win32.h"
+#define SECURITY_WIN32
+#include <security.h>
+#include <schannel.h>
+
+#include <iomanip>
+#include <vector>
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/schanneladapter.h"
+#include "talk/base/sec_buffer.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+/////////////////////////////////////////////////////////////////////////////
+// SChannelAdapter
+/////////////////////////////////////////////////////////////////////////////
+
+extern const ConstantLabel SECURITY_ERRORS[];
+
+const ConstantLabel SCHANNEL_BUFFER_TYPES[] = {
+  KLABEL(SECBUFFER_EMPTY),              //  0
+  KLABEL(SECBUFFER_DATA),               //  1
+  KLABEL(SECBUFFER_TOKEN),              //  2
+  KLABEL(SECBUFFER_PKG_PARAMS),         //  3
+  KLABEL(SECBUFFER_MISSING),            //  4
+  KLABEL(SECBUFFER_EXTRA),              //  5
+  KLABEL(SECBUFFER_STREAM_TRAILER),     //  6
+  KLABEL(SECBUFFER_STREAM_HEADER),      //  7
+  KLABEL(SECBUFFER_MECHLIST),           // 11
+  KLABEL(SECBUFFER_MECHLIST_SIGNATURE), // 12
+  KLABEL(SECBUFFER_TARGET),             // 13
+  KLABEL(SECBUFFER_CHANNEL_BINDINGS),   // 14
+  LASTLABEL
+};
+
+void DescribeBuffer(LoggingSeverity severity, const char* prefix,
+                    const SecBuffer& sb) {
+  LOG_V(severity)
+    << prefix
+    << "(" << sb.cbBuffer
+    << ", " << FindLabel(sb.BufferType & ~SECBUFFER_ATTRMASK,
+                          SCHANNEL_BUFFER_TYPES)
+    << ", " << sb.pvBuffer << ")";
+}
+
+void DescribeBuffers(LoggingSeverity severity, const char* prefix,
+                     const SecBufferDesc* sbd) {
+  if (!LOG_CHECK_LEVEL_V(severity))
+    return;
+  LOG_V(severity) << prefix << "(";
+  for (size_t i=0; i<sbd->cBuffers; ++i) {
+    DescribeBuffer(severity, "  ", sbd->pBuffers[i]);
+  }
+  LOG_V(severity) << ")";
+}
+
+const ULONG SSL_FLAGS_DEFAULT = ISC_REQ_ALLOCATE_MEMORY
+                              | ISC_REQ_CONFIDENTIALITY
+                              | ISC_REQ_EXTENDED_ERROR
+                              | ISC_REQ_INTEGRITY
+                              | ISC_REQ_REPLAY_DETECT
+                              | ISC_REQ_SEQUENCE_DETECT
+                              | ISC_REQ_STREAM;
+                              //| ISC_REQ_USE_SUPPLIED_CREDS;
+
+typedef std::vector<char> SChannelBuffer;
+
+struct SChannelAdapter::SSLImpl {
+  CredHandle cred;
+  CtxtHandle ctx;
+  bool cred_init, ctx_init;
+  SChannelBuffer inbuf, outbuf, readable;
+  SecPkgContext_StreamSizes sizes;
+
+  SSLImpl() : cred_init(false), ctx_init(false) { }
+};
+
+SChannelAdapter::SChannelAdapter(AsyncSocket* socket)
+  : SSLAdapter(socket), state_(SSL_NONE),
+    restartable_(false), signal_close_(false), message_pending_(false),
+    impl_(new SSLImpl) {
+}
+
+SChannelAdapter::~SChannelAdapter() {
+  Cleanup();
+}
+
+int
+SChannelAdapter::StartSSL(const char* hostname, bool restartable) {
+  if (state_ != SSL_NONE)
+    return ERROR_ALREADY_INITIALIZED;
+
+  ssl_host_name_ = hostname;
+  restartable_ = restartable;
+
+  if (socket_->GetState() != Socket::CS_CONNECTED) {
+    state_ = SSL_WAIT;
+    return 0;
+  }
+
+  state_ = SSL_CONNECTING;
+  if (int err = BeginSSL()) {
+    Error("BeginSSL", err, false);
+    return err;
+  }
+
+  return 0;
+}
+
+int
+SChannelAdapter::BeginSSL() {
+  LOG(LS_VERBOSE) << "BeginSSL: " << ssl_host_name_;
+  ASSERT(state_ == SSL_CONNECTING);
+
+  SECURITY_STATUS ret;
+
+  SCHANNEL_CRED sc_cred = { 0 };
+  sc_cred.dwVersion = SCHANNEL_CRED_VERSION;
+  //sc_cred.dwMinimumCipherStrength = 128; // Note: use system default
+  sc_cred.dwFlags = SCH_CRED_NO_DEFAULT_CREDS | SCH_CRED_AUTO_CRED_VALIDATION;
+
+  ret = AcquireCredentialsHandle(NULL, UNISP_NAME, SECPKG_CRED_OUTBOUND, NULL,
+                                 &sc_cred, NULL, NULL, &impl_->cred, NULL);
+  if (ret != SEC_E_OK) {
+    LOG(LS_ERROR) << "AcquireCredentialsHandle error: "
+                  << ErrorName(ret, SECURITY_ERRORS);
+    return ret;
+  }
+  impl_->cred_init = true;
+
+  if (LOG_CHECK_LEVEL(LS_VERBOSE)) {
+    SecPkgCred_CipherStrengths cipher_strengths = { 0 };
+    ret = QueryCredentialsAttributes(&impl_->cred,
+                                     SECPKG_ATTR_CIPHER_STRENGTHS,
+                                     &cipher_strengths);
+    if (SUCCEEDED(ret)) {
+      LOG(LS_VERBOSE) << "SChannel cipher strength: "
+                  << cipher_strengths.dwMinimumCipherStrength << " - "
+                  << cipher_strengths.dwMaximumCipherStrength;
+    }
+
+    SecPkgCred_SupportedAlgs supported_algs = { 0 };
+    ret = QueryCredentialsAttributes(&impl_->cred,
+                                     SECPKG_ATTR_SUPPORTED_ALGS,
+                                     &supported_algs);
+    if (SUCCEEDED(ret)) {
+      LOG(LS_VERBOSE) << "SChannel supported algorithms:";
+      for (DWORD i=0; i<supported_algs.cSupportedAlgs; ++i) {
+        ALG_ID alg_id = supported_algs.palgSupportedAlgs[i];
+        PCCRYPT_OID_INFO oinfo = CryptFindOIDInfo(CRYPT_OID_INFO_ALGID_KEY,
+                                                  &alg_id, 0);
+        LPCWSTR alg_name = (NULL != oinfo) ? oinfo->pwszName : L"Unknown";
+        LOG(LS_VERBOSE) << "  " << ToUtf8(alg_name) << " (" << alg_id << ")";
+      }
+      CSecBufferBase::FreeSSPI(supported_algs.palgSupportedAlgs);
+    }
+  }
+
+  ULONG flags = SSL_FLAGS_DEFAULT, ret_flags = 0;
+  if (ignore_bad_cert())
+    flags |= ISC_REQ_MANUAL_CRED_VALIDATION;
+
+  CSecBufferBundle<2, CSecBufferBase::FreeSSPI> sb_out;
+  ret = InitializeSecurityContextA(&impl_->cred, NULL,
+                                   const_cast<char*>(ssl_host_name_.c_str()),
+                                   flags, 0, 0, NULL, 0,
+                                   &impl_->ctx, sb_out.desc(),
+                                   &ret_flags, NULL);
+  if (SUCCEEDED(ret))
+    impl_->ctx_init = true;
+  return ProcessContext(ret, NULL, sb_out.desc());
+}
+
+int
+SChannelAdapter::ContinueSSL() {
+  LOG(LS_VERBOSE) << "ContinueSSL";
+  ASSERT(state_ == SSL_CONNECTING);
+
+  SECURITY_STATUS ret;
+
+  CSecBufferBundle<2> sb_in;
+  sb_in[0].BufferType = SECBUFFER_TOKEN;
+  sb_in[0].cbBuffer = static_cast<unsigned long>(impl_->inbuf.size());
+  sb_in[0].pvBuffer = &impl_->inbuf[0];
+  //DescribeBuffers(LS_VERBOSE, "Input Buffer ", sb_in.desc());
+
+  ULONG flags = SSL_FLAGS_DEFAULT, ret_flags = 0;
+  if (ignore_bad_cert())
+    flags |= ISC_REQ_MANUAL_CRED_VALIDATION;
+
+  CSecBufferBundle<2, CSecBufferBase::FreeSSPI> sb_out;
+  ret = InitializeSecurityContextA(&impl_->cred, &impl_->ctx,
+                                   const_cast<char*>(ssl_host_name_.c_str()),
+                                   flags, 0, 0, sb_in.desc(), 0,
+                                   NULL, sb_out.desc(),
+                                   &ret_flags, NULL);
+  return ProcessContext(ret, sb_in.desc(), sb_out.desc());
+}
+
+int
+SChannelAdapter::ProcessContext(long int status, _SecBufferDesc* sbd_in,
+                                _SecBufferDesc* sbd_out) {
+  LoggingSeverity level = LS_ERROR;
+  if ((status == SEC_E_OK)
+      || (status != SEC_I_CONTINUE_NEEDED)
+      || (status != SEC_E_INCOMPLETE_MESSAGE)) {
+    level = LS_VERBOSE;  // Expected messages
+  }
+  LOG_V(level)
+    << "InitializeSecurityContext error: "
+    << ErrorName(status, SECURITY_ERRORS);
+  //if (sbd_in)
+  //  DescribeBuffers(LS_VERBOSE, "Input Buffer ", sbd_in);
+  //if (sbd_out)
+  //  DescribeBuffers(LS_VERBOSE, "Output Buffer ", sbd_out);
+
+  if (status == SEC_E_INCOMPLETE_MESSAGE) {
+    // Wait for more input from server.
+    return Flush();
+  }
+
+  if (FAILED(status)) {
+    // We can't continue.  Common errors:
+    // SEC_E_CERT_EXPIRED - Typically, this means the computer clock is wrong.
+    return status;
+  }
+
+  // Note: we check both input and output buffers for SECBUFFER_EXTRA.
+  // Experience shows it appearing in the input, but the documentation claims
+  // it should appear in the output.
+  size_t extra = 0;
+  if (sbd_in) {
+    for (size_t i=0; i<sbd_in->cBuffers; ++i) {
+      SecBuffer& buffer = sbd_in->pBuffers[i];
+      if (buffer.BufferType == SECBUFFER_EXTRA) {
+        extra += buffer.cbBuffer;
+      }
+    }
+  }
+  if (sbd_out) {
+    for (size_t i=0; i<sbd_out->cBuffers; ++i) {
+      SecBuffer& buffer = sbd_out->pBuffers[i];
+      if (buffer.BufferType == SECBUFFER_EXTRA) {
+        extra += buffer.cbBuffer;
+      } else if (buffer.BufferType == SECBUFFER_TOKEN) {
+        impl_->outbuf.insert(impl_->outbuf.end(),
+          reinterpret_cast<char*>(buffer.pvBuffer),
+          reinterpret_cast<char*>(buffer.pvBuffer) + buffer.cbBuffer);
+      }
+    }
+  }
+
+  if (extra) {
+    ASSERT(extra <= impl_->inbuf.size());
+    size_t consumed = impl_->inbuf.size() - extra;
+    memmove(&impl_->inbuf[0], &impl_->inbuf[consumed], extra);
+    impl_->inbuf.resize(extra);
+  } else {
+    impl_->inbuf.clear();
+  }
+
+  if (SEC_I_CONTINUE_NEEDED == status) {
+    // Send data to server and wait for response.
+    // Note: ContinueSSL will result in a Flush, anyway.
+    return impl_->inbuf.empty() ? Flush() : ContinueSSL();
+  }
+
+  if (SEC_E_OK == status) {
+    LOG(LS_VERBOSE) << "QueryContextAttributes";
+    status = QueryContextAttributes(&impl_->ctx, SECPKG_ATTR_STREAM_SIZES,
+                                    &impl_->sizes);
+    if (FAILED(status)) {
+      LOG(LS_ERROR) << "QueryContextAttributes error: "
+                    << ErrorName(status, SECURITY_ERRORS);
+      return status;
+    }
+
+    state_ = SSL_CONNECTED;
+
+    if (int err = DecryptData()) {
+      return err;
+    } else if (int err = Flush()) {
+      return err;
+    } else {
+      // If we decrypted any data, queue up a notification here
+      PostEvent();
+      // Signal our connectedness
+      AsyncSocketAdapter::OnConnectEvent(this);
+    }
+    return 0;
+  }
+
+  if (SEC_I_INCOMPLETE_CREDENTIALS == status) {
+    // We don't support client authentication in schannel.
+    return status;
+  }
+
+  // We don't expect any other codes
+  ASSERT(false);
+  return status;
+}
+
+int
+SChannelAdapter::DecryptData() {
+  SChannelBuffer& inbuf = impl_->inbuf;
+  SChannelBuffer& readable = impl_->readable;
+
+  while (!inbuf.empty()) {
+    CSecBufferBundle<4> in_buf;
+    in_buf[0].BufferType = SECBUFFER_DATA;
+    in_buf[0].cbBuffer = static_cast<unsigned long>(inbuf.size());
+    in_buf[0].pvBuffer = &inbuf[0];
+
+    //DescribeBuffers(LS_VERBOSE, "Decrypt In ", in_buf.desc());
+    SECURITY_STATUS status = DecryptMessage(&impl_->ctx, in_buf.desc(), 0, 0);
+    //DescribeBuffers(LS_VERBOSE, "Decrypt Out ", in_buf.desc());
+
+    // Note: We are explicitly treating SEC_E_OK, SEC_I_CONTEXT_EXPIRED, and
+    // any other successful results as continue.
+    if (SUCCEEDED(status)) {
+      size_t data_len = 0, extra_len = 0;
+      for (size_t i=0; i<in_buf.desc()->cBuffers; ++i) {
+        if (in_buf[i].BufferType == SECBUFFER_DATA) {
+          data_len += in_buf[i].cbBuffer;
+          readable.insert(readable.end(),
+            reinterpret_cast<char*>(in_buf[i].pvBuffer),
+            reinterpret_cast<char*>(in_buf[i].pvBuffer) + in_buf[i].cbBuffer);
+        } else if (in_buf[i].BufferType == SECBUFFER_EXTRA) {
+          extra_len += in_buf[i].cbBuffer;
+        }
+      }
+      // There is a bug on Win2K where SEC_I_CONTEXT_EXPIRED is misclassified.
+      if ((data_len == 0) && (inbuf[0] == 0x15)) {
+        status = SEC_I_CONTEXT_EXPIRED;
+      }
+      if (extra_len) {
+        size_t consumed = inbuf.size() - extra_len;
+        memmove(&inbuf[0], &inbuf[consumed], extra_len);
+        inbuf.resize(extra_len);
+      } else {
+        inbuf.clear();
+      }
+      // TODO: Handle SEC_I_CONTEXT_EXPIRED to do clean shutdown
+      if (status != SEC_E_OK) {
+        LOG(LS_INFO) << "DecryptMessage returned continuation code: "
+                      << ErrorName(status, SECURITY_ERRORS);
+      }
+      continue;
+    }
+
+    if (status == SEC_E_INCOMPLETE_MESSAGE) {
+      break;
+    } else {
+      return status;
+    }
+  }
+
+  return 0;
+}
+
+void
+SChannelAdapter::Cleanup() {
+  if (impl_->ctx_init)
+    DeleteSecurityContext(&impl_->ctx);
+  if (impl_->cred_init)
+    FreeCredentialsHandle(&impl_->cred);
+  delete impl_;
+}
+
+void
+SChannelAdapter::PostEvent() {
+  // Check if there's anything notable to signal
+  if (impl_->readable.empty() && !signal_close_)
+    return;
+
+  // Only one post in the queue at a time
+  if (message_pending_)
+    return;
+
+  if (Thread* thread = Thread::Current()) {
+    message_pending_ = true;
+    thread->Post(this);
+  } else {
+    LOG(LS_ERROR) << "No thread context available for SChannelAdapter";
+    ASSERT(false);
+  }
+}
+
+void
+SChannelAdapter::Error(const char* context, int err, bool signal) {
+  LOG(LS_WARNING) << "SChannelAdapter::Error("
+                  << context << ", "
+                  << ErrorName(err, SECURITY_ERRORS) << ")";
+  state_ = SSL_ERROR;
+  SetError(err);
+  if (signal)
+    AsyncSocketAdapter::OnCloseEvent(this, err);
+}
+
+int
+SChannelAdapter::Read() {
+  char buffer[4096];
+  SChannelBuffer& inbuf = impl_->inbuf;
+  while (true) {
+    int ret = AsyncSocketAdapter::Recv(buffer, sizeof(buffer));
+    if (ret > 0) {
+      inbuf.insert(inbuf.end(), buffer, buffer + ret);
+    } else if (GetError() == EWOULDBLOCK) {
+      return 0;  // Blocking
+    } else {
+      return GetError();
+    }
+  }
+}
+
+int
+SChannelAdapter::Flush() {
+  int result = 0;
+  size_t pos = 0;
+  SChannelBuffer& outbuf = impl_->outbuf;
+  while (pos < outbuf.size()) {
+    int sent = AsyncSocketAdapter::Send(&outbuf[pos], outbuf.size() - pos);
+    if (sent > 0) {
+      pos += sent;
+    } else if (GetError() == EWOULDBLOCK) {
+      break;  // Blocking
+    } else {
+      result = GetError();
+      break;
+    }
+  }
+  if (int remainder = outbuf.size() - pos) {
+    memmove(&outbuf[0], &outbuf[pos], remainder);
+    outbuf.resize(remainder);
+  } else {
+    outbuf.clear();
+  }
+  return result;
+}
+
+//
+// AsyncSocket Implementation
+//
+
+int
+SChannelAdapter::Send(const void* pv, size_t cb) {
+  switch (state_) {
+  case SSL_NONE:
+    return AsyncSocketAdapter::Send(pv, cb);
+
+  case SSL_WAIT:
+  case SSL_CONNECTING:
+    SetError(EWOULDBLOCK);
+    return SOCKET_ERROR;
+
+  case SSL_CONNECTED:
+    break;
+
+  case SSL_ERROR:
+  default:
+    return SOCKET_ERROR;
+  }
+
+  size_t written = 0;
+  SChannelBuffer& outbuf = impl_->outbuf;
+  while (written < cb) {
+    const size_t encrypt_len = std::min<size_t>(cb - written,
+                                                impl_->sizes.cbMaximumMessage);
+
+    CSecBufferBundle<4> out_buf;
+    out_buf[0].BufferType = SECBUFFER_STREAM_HEADER;
+    out_buf[0].cbBuffer = impl_->sizes.cbHeader;
+    out_buf[1].BufferType = SECBUFFER_DATA;
+    out_buf[1].cbBuffer = static_cast<unsigned long>(encrypt_len);
+    out_buf[2].BufferType = SECBUFFER_STREAM_TRAILER;
+    out_buf[2].cbBuffer = impl_->sizes.cbTrailer;
+
+    size_t packet_len = out_buf[0].cbBuffer
+                      + out_buf[1].cbBuffer
+                      + out_buf[2].cbBuffer;
+
+    SChannelBuffer message;
+    message.resize(packet_len);
+    out_buf[0].pvBuffer = &message[0];
+    out_buf[1].pvBuffer = &message[out_buf[0].cbBuffer];
+    out_buf[2].pvBuffer = &message[out_buf[0].cbBuffer + out_buf[1].cbBuffer];
+
+    memcpy(out_buf[1].pvBuffer,
+           static_cast<const char*>(pv) + written,
+           encrypt_len);
+
+    //DescribeBuffers(LS_VERBOSE, "Encrypt In ", out_buf.desc());
+    SECURITY_STATUS res = EncryptMessage(&impl_->ctx, 0, out_buf.desc(), 0);
+    //DescribeBuffers(LS_VERBOSE, "Encrypt Out ", out_buf.desc());
+
+    if (FAILED(res)) {
+      Error("EncryptMessage", res, false);
+      return SOCKET_ERROR;
+    }
+
+    // We assume that the header and data segments do not change length,
+    // or else encrypting the concatenated packet in-place is wrong.
+    ASSERT(out_buf[0].cbBuffer == impl_->sizes.cbHeader);
+    ASSERT(out_buf[1].cbBuffer == static_cast<unsigned long>(encrypt_len));
+
+    // However, the length of the trailer may change due to padding.
+    ASSERT(out_buf[2].cbBuffer <= impl_->sizes.cbTrailer);
+
+    packet_len = out_buf[0].cbBuffer
+               + out_buf[1].cbBuffer
+               + out_buf[2].cbBuffer;
+
+    written += encrypt_len;
+    outbuf.insert(outbuf.end(), &message[0], &message[packet_len-1]+1);
+  }
+
+  if (int err = Flush()) {
+    state_ = SSL_ERROR;
+    SetError(err);
+    return SOCKET_ERROR;
+  }
+
+  return static_cast<int>(written);
+}
+
+int
+SChannelAdapter::Recv(void* pv, size_t cb) {
+  switch (state_) {
+  case SSL_NONE:
+    return AsyncSocketAdapter::Recv(pv, cb);
+
+  case SSL_WAIT:
+  case SSL_CONNECTING:
+    SetError(EWOULDBLOCK);
+    return SOCKET_ERROR;
+
+  case SSL_CONNECTED:
+    break;
+
+  case SSL_ERROR:
+  default:
+    return SOCKET_ERROR;
+  }
+
+  SChannelBuffer& readable = impl_->readable;
+  if (readable.empty()) {
+    SetError(EWOULDBLOCK);
+    return SOCKET_ERROR;
+  }
+  size_t read = _min(cb, readable.size());
+  memcpy(pv, &readable[0], read);
+  if (size_t remaining = readable.size() - read) {
+    memmove(&readable[0], &readable[read], remaining);
+    readable.resize(remaining);
+  } else {
+    readable.clear();
+  }
+
+  PostEvent();
+  return static_cast<int>(read);
+}
+
+int
+SChannelAdapter::Close() {
+  if (!impl_->readable.empty()) {
+    LOG(WARNING) << "SChannelAdapter::Close with readable data";
+    // Note: this isn't strictly an error, but we're using it temporarily to
+    // track bugs.
+    //ASSERT(false);
+  }
+  if (state_ == SSL_CONNECTED) {
+    DWORD token = SCHANNEL_SHUTDOWN;
+    CSecBufferBundle<1> sb_in;
+    sb_in[0].BufferType = SECBUFFER_TOKEN;
+    sb_in[0].cbBuffer = sizeof(token);
+    sb_in[0].pvBuffer = &token;
+    ApplyControlToken(&impl_->ctx, sb_in.desc());
+    // TODO: In theory, to do a nice shutdown, we need to begin shutdown
+    // negotiation with more calls to InitializeSecurityContext.  Since the
+    // socket api doesn't support nice shutdown at this point, we don't bother.
+  }
+  Cleanup();
+  impl_ = new SSLImpl;
+  state_ = restartable_ ? SSL_WAIT : SSL_NONE;
+  signal_close_ = false;
+  message_pending_ = false;
+  return AsyncSocketAdapter::Close();
+}
+
+Socket::ConnState
+SChannelAdapter::GetState() const {
+  if (signal_close_)
+    return CS_CONNECTED;
+  ConnState state = socket_->GetState();
+  if ((state == CS_CONNECTED)
+      && ((state_ == SSL_WAIT) || (state_ == SSL_CONNECTING)))
+    state = CS_CONNECTING;
+  return state;
+}
+
+void
+SChannelAdapter::OnConnectEvent(AsyncSocket* socket) {
+  LOG(LS_VERBOSE) << "SChannelAdapter::OnConnectEvent";
+  if (state_ != SSL_WAIT) {
+    ASSERT(state_ == SSL_NONE);
+    AsyncSocketAdapter::OnConnectEvent(socket);
+    return;
+  }
+
+  state_ = SSL_CONNECTING;
+  if (int err = BeginSSL()) {
+    Error("BeginSSL", err);
+  }
+}
+
+void
+SChannelAdapter::OnReadEvent(AsyncSocket* socket) {
+  if (state_ == SSL_NONE) {
+    AsyncSocketAdapter::OnReadEvent(socket);
+    return;
+  }
+
+  if (int err = Read()) {
+    Error("Read", err);
+    return;
+  }
+
+  if (impl_->inbuf.empty())
+    return;
+
+  if (state_ == SSL_CONNECTED) {
+    if (int err = DecryptData()) {
+      Error("DecryptData", err);
+    } else if (!impl_->readable.empty()) {
+      AsyncSocketAdapter::OnReadEvent(this);
+    }
+  } else if (state_ == SSL_CONNECTING) {
+    if (int err = ContinueSSL()) {
+      Error("ContinueSSL", err);
+    }
+  }
+}
+
+void
+SChannelAdapter::OnWriteEvent(AsyncSocket* socket) {
+  if (state_ == SSL_NONE) {
+    AsyncSocketAdapter::OnWriteEvent(socket);
+    return;
+  }
+
+  if (int err = Flush()) {
+    Error("Flush", err);
+    return;
+  }
+
+  // See if we have more data to write
+  if (!impl_->outbuf.empty())
+    return;
+
+  // Buffer is empty, submit notification
+  if (state_ == SSL_CONNECTED) {
+    AsyncSocketAdapter::OnWriteEvent(socket);
+  }
+}
+
+void
+SChannelAdapter::OnCloseEvent(AsyncSocket* socket, int err) {
+  if ((state_ == SSL_NONE) || impl_->readable.empty()) {
+    AsyncSocketAdapter::OnCloseEvent(socket, err);
+    return;
+  }
+
+  // If readable is non-empty, then we have a pending Message
+  // that will allow us to signal close (eventually).
+  signal_close_ = true;
+}
+
+void
+SChannelAdapter::OnMessage(Message* pmsg) {
+  if (!message_pending_)
+    return;  // This occurs when socket is closed
+
+  message_pending_ = false;
+  if (!impl_->readable.empty()) {
+    AsyncSocketAdapter::OnReadEvent(this);
+  } else if (signal_close_) {
+    signal_close_ = false;
+    AsyncSocketAdapter::OnCloseEvent(this, 0); // TODO: cache this error?
+  }
+}
+
+} // namespace talk_base
diff --git a/talk/base/schanneladapter.h b/talk/base/schanneladapter.h
new file mode 100644
index 0000000..a5ab7b3
--- /dev/null
+++ b/talk/base/schanneladapter.h
@@ -0,0 +1,94 @@
+/*
+ * 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_BASE_SCHANNELADAPTER_H__
+#define TALK_BASE_SCHANNELADAPTER_H__
+
+#include <string>
+#include "talk/base/ssladapter.h"
+#include "talk/base/messagequeue.h"
+struct _SecBufferDesc;
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SChannelAdapter : public SSLAdapter, public MessageHandler {
+public:
+  SChannelAdapter(AsyncSocket* socket);
+  virtual ~SChannelAdapter();
+
+  virtual int StartSSL(const char* hostname, bool restartable);
+  virtual int Send(const void* pv, size_t cb);
+  virtual int Recv(void* pv, size_t cb);
+  virtual int Close();
+
+  // Note that the socket returns ST_CONNECTING while SSL is being negotiated.
+  virtual ConnState GetState() const;
+
+protected:
+  enum SSLState {
+    SSL_NONE, SSL_WAIT, SSL_CONNECTING, SSL_CONNECTED, SSL_ERROR
+  };
+  struct SSLImpl;
+
+  virtual void OnConnectEvent(AsyncSocket* socket);
+  virtual void OnReadEvent(AsyncSocket* socket);
+  virtual void OnWriteEvent(AsyncSocket* socket);
+  virtual void OnCloseEvent(AsyncSocket* socket, int err);
+  virtual void OnMessage(Message* pmsg);
+
+  int BeginSSL();
+  int ContinueSSL();
+  int ProcessContext(long int status, _SecBufferDesc* sbd_in,
+                     _SecBufferDesc* sbd_out);
+  int DecryptData();
+
+  int Read();
+  int Flush();
+  void Error(const char* context, int err, bool signal = true);
+  void Cleanup();
+
+  void PostEvent();
+
+private:
+  SSLState state_;
+  std::string ssl_host_name_;
+  // If true, socket will retain SSL configuration after Close.
+  bool restartable_; 
+  // If true, we are delaying signalling close until all data is read.
+  bool signal_close_;
+  // If true, we are waiting to be woken up to signal readability or closure.
+  bool message_pending_;
+  SSLImpl* impl_;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
+
+#endif // TALK_BASE_SCHANNELADAPTER_H__
diff --git a/talk/base/scoped_ptr.h b/talk/base/scoped_ptr.h
new file mode 100644
index 0000000..fd753ec
--- /dev/null
+++ b/talk/base/scoped_ptr.h
@@ -0,0 +1,258 @@
+//  (C) Copyright Greg Colvin and Beman Dawes 1998, 1999.
+//  Copyright (c) 2001, 2002 Peter Dimov
+//
+//  Permission to copy, use, modify, sell and distribute this software
+//  is granted provided this copyright notice appears in all copies.
+//  This software is provided "as is" without express or implied
+//  warranty, and with no claim as to its suitability for any purpose.
+//
+//  See http://www.boost.org/libs/smart_ptr/scoped_ptr.htm for documentation.
+//
+
+//  scoped_ptr mimics a built-in pointer except that it guarantees deletion
+//  of the object pointed to, either on destruction of the scoped_ptr or via
+//  an explicit reset(). scoped_ptr is a simple solution for simple needs;
+//  use shared_ptr or std::auto_ptr if your needs are more complex.
+
+//  scoped_ptr_malloc added in by Google.  When one of
+//  these goes out of scope, instead of doing a delete or delete[], it
+//  calls free().  scoped_ptr_malloc<char> is likely to see much more
+//  use than any other specializations.
+
+//  release() added in by Google. Use this to conditionally
+//  transfer ownership of a heap-allocated object to the caller, usually on
+//  method success.
+#ifndef TALK_BASE_SCOPED_PTR_H__
+#define TALK_BASE_SCOPED_PTR_H__
+
+#include <cstddef>             // for std::ptrdiff_t
+#include <stdlib.h>            // for free() decl
+
+#include "talk/base/common.h"  // for ASSERT
+
+#ifdef _WIN32
+namespace std { using ::ptrdiff_t; };
+#endif // _WIN32
+
+namespace talk_base {
+
+template <typename T>
+class scoped_ptr {
+ private:
+
+  T* ptr;
+
+  scoped_ptr(scoped_ptr const &);
+  scoped_ptr & operator=(scoped_ptr const &);
+
+ public:
+
+  typedef T element_type;
+
+  explicit scoped_ptr(T* p = NULL): ptr(p) {}
+
+  ~scoped_ptr() {
+    typedef char type_must_be_complete[sizeof(T)];
+    delete ptr;
+  }
+
+  void reset(T* p = NULL) {
+    typedef char type_must_be_complete[sizeof(T)];
+
+    if (ptr != p) {
+      T* obj = ptr;
+      ptr = p;
+      // Delete last, in case obj destructor indirectly results in ~scoped_ptr
+      delete obj;
+    }
+  }
+
+  T& operator*() const {
+    ASSERT(ptr != NULL);
+    return *ptr;
+  }
+
+  T* operator->() const  {
+    ASSERT(ptr != NULL);
+    return ptr;
+  }
+
+  T* get() const  {
+    return ptr;
+  }
+
+  void swap(scoped_ptr & b) {
+    T* tmp = b.ptr;
+    b.ptr = ptr;
+    ptr = tmp;
+  }
+
+  T* release() {
+    T* tmp = ptr;
+    ptr = NULL;
+    return tmp;
+  }
+
+  T** accept() {
+    if (ptr) {
+      delete ptr;
+      ptr = NULL;
+    }
+    return &ptr;
+  }
+
+  T** use() {
+    return &ptr;
+  }
+};
+
+template<typename T> inline
+void swap(scoped_ptr<T>& a, scoped_ptr<T>& b) {
+  a.swap(b);
+}
+
+
+
+
+//  scoped_array extends scoped_ptr to arrays. Deletion of the array pointed to
+//  is guaranteed, either on destruction of the scoped_array or via an explicit
+//  reset(). Use shared_array or std::vector if your needs are more complex.
+
+template<typename T>
+class scoped_array {
+ private:
+
+  T* ptr;
+
+  scoped_array(scoped_array const &);
+  scoped_array & operator=(scoped_array const &);
+
+ public:
+
+  typedef T element_type;
+
+  explicit scoped_array(T* p = NULL) : ptr(p) {}
+
+  ~scoped_array() {
+    typedef char type_must_be_complete[sizeof(T)];
+    delete[] ptr;
+  }
+
+  void reset(T* p = NULL) {
+    typedef char type_must_be_complete[sizeof(T)];
+
+    if (ptr != p) {
+      T* arr = ptr;
+      ptr = p;
+      // Delete last, in case arr destructor indirectly results in ~scoped_array
+      delete [] arr;
+    }
+  }
+
+  T& operator[](std::ptrdiff_t i) const {
+    ASSERT(ptr != NULL);
+    ASSERT(i >= 0);
+    return ptr[i];
+  }
+
+  T* get() const {
+    return ptr;
+  }
+
+  void swap(scoped_array & b) {
+    T* tmp = b.ptr;
+    b.ptr = ptr;
+    ptr = tmp;
+  }
+
+  T* release() {
+    T* tmp = ptr;
+    ptr = NULL;
+    return tmp;
+  }
+
+  T** accept() {
+    if (ptr) {
+      delete [] ptr;
+      ptr = NULL;
+    }
+    return &ptr;
+  }
+};
+
+template<class T> inline
+void swap(scoped_array<T>& a, scoped_array<T>& b) {
+  a.swap(b);
+}
+
+// scoped_ptr_malloc<> is similar to scoped_ptr<>, but it accepts a
+// second template argument, the function used to free the object.
+
+template<typename T, void (*FF)(void*) = free> class scoped_ptr_malloc {
+ private:
+
+  T* ptr;
+
+  scoped_ptr_malloc(scoped_ptr_malloc const &);
+  scoped_ptr_malloc & operator=(scoped_ptr_malloc const &);
+
+ public:
+
+  typedef T element_type;
+
+  explicit scoped_ptr_malloc(T* p = 0): ptr(p) {}
+
+  ~scoped_ptr_malloc() {
+    FF(static_cast<void*>(ptr));
+  }
+
+  void reset(T* p = 0) {
+    if (ptr != p) {
+      FF(static_cast<void*>(ptr));
+      ptr = p;
+    }
+  }
+
+  T& operator*() const {
+    ASSERT(ptr != 0);
+    return *ptr;
+  }
+
+  T* operator->() const {
+    ASSERT(ptr != 0);
+    return ptr;
+  }
+
+  T* get() const {
+    return ptr;
+  }
+
+  void swap(scoped_ptr_malloc & b) {
+    T* tmp = b.ptr;
+    b.ptr = ptr;
+    ptr = tmp;
+  }
+
+  T* release() {
+    T* tmp = ptr;
+    ptr = 0;
+    return tmp;
+  }
+
+  T** accept() {
+    if (ptr) {
+      FF(static_cast<void*>(ptr));
+      ptr = 0;
+    }
+    return &ptr;
+  }
+};
+
+template<typename T, void (*FF)(void*)> inline
+void swap(scoped_ptr_malloc<T,FF>& a, scoped_ptr_malloc<T,FF>& b) {
+  a.swap(b);
+}
+
+} // namespace talk_base
+
+#endif  // #ifndef TALK_BASE_SCOPED_PTR_H__
diff --git a/talk/base/sec_buffer.h b/talk/base/sec_buffer.h
new file mode 100644
index 0000000..585e27f
--- /dev/null
+++ b/talk/base/sec_buffer.h
@@ -0,0 +1,173 @@
+/*
+ * 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.
+ */
+
+// @file Contains utility classes that make it easier to use SecBuffers
+
+#ifndef TALK_BASE_SEC_BUFFER_H__
+#define TALK_BASE_SEC_BUFFER_H__
+
+namespace talk_base {
+
+// A base class for CSecBuffer<T>. Contains
+// all implementation that does not require
+// template arguments.
+class CSecBufferBase : public SecBuffer {
+ public:
+  CSecBufferBase() {
+    Clear();
+  }
+
+  // Uses the SSPI to free a pointer, must be
+  // used for buffers returned from SSPI APIs.
+  static void FreeSSPI(void *ptr) {
+    if ( ptr ) {
+      SECURITY_STATUS status;
+      status = ::FreeContextBuffer(ptr);
+      ASSERT(SEC_E_OK == status); // "Freeing context buffer"
+    }
+  }
+
+  // Deletes a buffer with operator delete
+  static void FreeDelete(void *ptr) {
+    delete [] reinterpret_cast<char*>(ptr);
+  }
+
+  // A noop delete, for buffers over other
+  // people's memory
+  static void FreeNone(void *ptr) {
+  }
+
+ protected:
+  // Clears the buffer to EMPTY & NULL
+  void Clear() {
+    this->BufferType = SECBUFFER_EMPTY;
+    this->cbBuffer = 0;
+    this->pvBuffer = NULL;
+  }
+};
+
+// Wrapper class for SecBuffer to take care
+// of initialization and destruction.
+template <void (*pfnFreeBuffer)(void *ptr)>
+class CSecBuffer: public CSecBufferBase {
+ public:
+  // Initializes buffer to empty & NULL
+  CSecBuffer() {
+  }
+
+  // Frees any allocated memory
+  ~CSecBuffer() {
+    Release();
+  }
+
+  // Frees the buffer appropriately, and re-nulls
+  void Release() {
+    pfnFreeBuffer(this->pvBuffer);
+    Clear();
+  }
+
+ private:
+  // A placeholder function for compile-time asserts on the class
+  void CompileAsserts() {
+    // never invoked...
+    assert(false); // _T("Notreached")
+
+    // This class must not extend the size of SecBuffer, since
+    // we use arrays of CSecBuffer in CSecBufferBundle below
+    cassert(sizeof(CSecBuffer<SSPIFree> == sizeof(SecBuffer)));
+  }
+};
+
+// Contains all generic implementation for the
+// SecBufferBundle class
+class SecBufferBundleBase {
+ public:
+};
+
+// A template class that bundles a SecBufferDesc with
+// one or more SecBuffers for convenience. Can take
+// care of deallocating buffers appropriately, as indicated
+// by pfnFreeBuffer function.
+// By default does no deallocation.
+template <int num_buffers,
+          void (*pfnFreeBuffer)(void *ptr) = CSecBufferBase::FreeNone>
+class CSecBufferBundle : public SecBufferBundleBase {
+ public:
+  // Constructs a security buffer bundle with num_buffers
+  // buffers, all of which are empty and nulled.
+  CSecBufferBundle() {
+    desc_.ulVersion = SECBUFFER_VERSION;
+    desc_.cBuffers = num_buffers;
+    desc_.pBuffers = buffers_;
+  }
+
+  // Frees all currently used buffers.
+  ~CSecBufferBundle() {
+    Release();
+  }
+
+  // Accessor for the descriptor
+  PSecBufferDesc desc() {
+    return &desc_;
+  }
+
+  // Accessor for the descriptor
+  const PSecBufferDesc desc() const {
+    return &desc_;
+  }
+
+  // returns the i-th security buffer
+  SecBuffer &operator[] (size_t num) {
+    ASSERT(num < num_buffers); // "Buffer index out of bounds"
+    return buffers_[num];
+  }
+
+  // returns the i-th security buffer
+  const SecBuffer &operator[] (size_t num) const {
+    ASSERT(num < num_buffers); // "Buffer index out of bounds"
+    return buffers_[num];
+  }
+
+  // Frees all non-NULL security buffers,
+  // using the deallocation function
+  void Release() {
+    for ( size_t i = 0; i < num_buffers; ++i ) {
+      buffers_[i].Release();
+    }
+  }
+
+ private:
+  // Our descriptor
+  SecBufferDesc               desc_;
+  // Our bundled buffers, each takes care of its own
+  // initialization and destruction
+  CSecBuffer<pfnFreeBuffer>   buffers_[num_buffers];
+};
+
+} // namespace talk_base
+
+#endif  // TALK_BASE_SEC_BUFFER_H__
diff --git a/talk/base/signalthread.cc b/talk/base/signalthread.cc
new file mode 100644
index 0000000..d6a86c0
--- /dev/null
+++ b/talk/base/signalthread.cc
@@ -0,0 +1,164 @@
+/*
+ * libjingle
+ * Copyright 2004--2009, 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/base/signalthread.h"
+
+#include "talk/base/common.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// SignalThread
+///////////////////////////////////////////////////////////////////////////////
+
+SignalThread::SignalThread() : main_(Thread::Current()), state_(kInit) {
+  main_->SignalQueueDestroyed.connect(this,
+                                      &SignalThread::OnMainThreadDestroyed);
+  refcount_ = 1;
+  worker_.parent_ = this;
+  worker_.SetName("SignalThread", this);
+}
+
+SignalThread::~SignalThread() {
+  ASSERT(refcount_ == 0);
+}
+
+bool SignalThread::SetName(const std::string& name, const void* obj) {
+  EnterExit ee(this);
+  ASSERT(main_->IsCurrent());
+  ASSERT(kInit == state_);
+  return worker_.SetName(name, obj);
+}
+
+bool SignalThread::SetPriority(ThreadPriority priority) {
+  EnterExit ee(this);
+  ASSERT(main_->IsCurrent());
+  ASSERT(kInit == state_);
+  return worker_.SetPriority(priority);
+}
+
+void SignalThread::Start() {
+  EnterExit ee(this);
+  ASSERT(main_->IsCurrent());
+  if (kInit == state_ || kComplete == state_) {
+    state_ = kRunning;
+    OnWorkStart();
+    worker_.Start();
+  } else {
+    ASSERT(false);
+  }
+}
+
+void SignalThread::Destroy(bool wait) {
+  EnterExit ee(this);
+  ASSERT(main_->IsCurrent());
+  if ((kInit == state_) || (kComplete == state_)) {
+    refcount_--;
+  } else if (kRunning == state_ || kReleasing == state_) {
+    state_ = kStopping;
+    // OnWorkStop() must follow Quit(), so that when the thread wakes up due to
+    // OWS(), ContinueWork() will return false.
+    worker_.Quit();
+    OnWorkStop();
+    if (wait) {
+      // Release the thread's lock so that it can return from ::Run.
+      cs_.Leave();
+      worker_.Stop();
+      cs_.Enter();
+      refcount_--;
+    }
+  } else {
+    ASSERT(false);
+  }
+}
+
+void SignalThread::Release() {
+  EnterExit ee(this);
+  ASSERT(main_->IsCurrent());
+  if (kComplete == state_) {
+    refcount_--;
+  } else if (kRunning == state_) {
+    state_ = kReleasing;
+  } else {
+    // if (kInit == state_) use Destroy()
+    ASSERT(false);
+  }
+}
+
+bool SignalThread::ContinueWork() {
+  EnterExit ee(this);
+  ASSERT(worker_.IsCurrent());
+  return worker_.ProcessMessages(0);
+}
+
+void SignalThread::OnMessage(Message *msg) {
+  EnterExit ee(this);
+  if (ST_MSG_WORKER_DONE == msg->message_id) {
+    ASSERT(main_->IsCurrent());
+    OnWorkDone();
+    bool do_delete = false;
+    if (kRunning == state_) {
+      state_ = kComplete;
+    } else {
+      do_delete = true;
+    }
+    if (kStopping != state_) {
+      // Before signaling that the work is done, make sure that the worker
+      // thread actually is done. We got here because DoWork() finished and
+      // Run() posted the ST_MSG_WORKER_DONE message. This means the worker
+      // thread is about to go away anyway, but sometimes it doesn't actually
+      // finish before SignalWorkDone is processed, and for a reusable
+      // SignalThread this makes an assert in thread.cc fire.
+      //
+      // Calling Stop() on the worker ensures that the OS thread that underlies
+      // the worker will finish, and will be set to NULL, enabling us to call
+      // Start() again.
+      worker_.Stop();
+      SignalWorkDone(this);
+    }
+    if (do_delete) {
+      refcount_--;
+    }
+  }
+}
+
+void SignalThread::Run() {
+  DoWork();
+  {
+    EnterExit ee(this);
+    if (main_) {
+      main_->Post(this, ST_MSG_WORKER_DONE);
+    }
+  }
+}
+
+void SignalThread::OnMainThreadDestroyed() {
+  EnterExit ee(this);
+  main_ = NULL;
+}
+
+}  // namespace talk_base
diff --git a/talk/base/signalthread.h b/talk/base/signalthread.h
new file mode 100644
index 0000000..99a6e69
--- /dev/null
+++ b/talk/base/signalthread.h
@@ -0,0 +1,158 @@
+/*
+ * libjingle
+ * Copyright 2004--2009, 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_BASE_SIGNALTHREAD_H_
+#define TALK_BASE_SIGNALTHREAD_H_
+
+#include <string>
+
+#include "talk/base/thread.h"
+#include "talk/base/sigslot.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// SignalThread - Base class for worker threads.  The main thread should call
+//  Start() to begin work, and then follow one of these models:
+//   Normal: Wait for SignalWorkDone, and then call Release to destroy.
+//   Cancellation: Call Release(true), to abort the worker thread.
+//   Fire-and-forget: Call Release(false), which allows the thread to run to
+//    completion, and then self-destruct without further notification.
+//   Periodic tasks: Wait for SignalWorkDone, then eventually call Start()
+//    again to repeat the task. When the instance isn't needed anymore,
+//    call Release. DoWork, OnWorkStart and OnWorkStop are called again,
+//    on a new thread.
+//  The subclass should override DoWork() to perform the background task.  By
+//   periodically calling ContinueWork(), it can check for cancellation.
+//   OnWorkStart and OnWorkDone can be overridden to do pre- or post-work
+//   tasks in the context of the main thread.
+///////////////////////////////////////////////////////////////////////////////
+
+class SignalThread : public sigslot::has_slots<>, protected MessageHandler {
+ public:
+  SignalThread();
+
+  // Context: Main Thread.  Call before Start to change the worker's name.
+  bool SetName(const std::string& name, const void* obj);
+
+  // Context: Main Thread.  Call before Start to change the worker's priority.
+  bool SetPriority(ThreadPriority priority);
+
+  // Context: Main Thread.  Call to begin the worker thread.
+  void Start();
+
+  // Context: Main Thread.  If the worker thread is not running, deletes the
+  // object immediately.  Otherwise, asks the worker thread to abort processing,
+  // and schedules the object to be deleted once the worker exits.
+  // SignalWorkDone will not be signalled.  If wait is true, does not return
+  // until the thread is deleted.
+  void Destroy(bool wait);
+
+  // Context: Main Thread.  If the worker thread is complete, deletes the
+  // object immediately.  Otherwise, schedules the object to be deleted once
+  // the worker thread completes.  SignalWorkDone will be signalled.
+  void Release();
+
+  // Context: Main Thread.  Signalled when work is complete.
+  sigslot::signal1<SignalThread *> SignalWorkDone;
+
+  enum { ST_MSG_WORKER_DONE, ST_MSG_FIRST_AVAILABLE };
+
+ protected:
+  virtual ~SignalThread();
+
+  Thread* worker() { return &worker_; }
+
+  // Context: Main Thread.  Subclass should override to do pre-work setup.
+  virtual void OnWorkStart() { }
+
+  // Context: Worker Thread.  Subclass should override to do work.
+  virtual void DoWork() = 0;
+
+  // Context: Worker Thread.  Subclass should call periodically to
+  // dispatch messages and determine if the thread should terminate.
+  bool ContinueWork();
+
+  // Context: Worker Thread.  Subclass should override when extra work is
+  // needed to abort the worker thread.
+  virtual void OnWorkStop() { }
+
+  // Context: Main Thread.  Subclass should override to do post-work cleanup.
+  virtual void OnWorkDone() { }
+
+  // Context: Any Thread.  If subclass overrides, be sure to call the base
+  // implementation.  Do not use (message_id < ST_MSG_FIRST_AVAILABLE)
+  virtual void OnMessage(Message *msg);
+
+ private:
+  enum State {
+    kInit,            // Initialized, but not started
+    kRunning,         // Started and doing work
+    kReleasing,       // Same as running, but to be deleted when work is done
+    kComplete,        // Work is done
+    kStopping,        // Work is being interrupted
+  };
+
+  friend class Worker;
+  class Worker : public Thread {
+   public:
+    SignalThread* parent_;
+    virtual void Run() { parent_->Run(); }
+  };
+
+  friend class EnterExit;
+  class EnterExit {
+   public:
+    explicit EnterExit(SignalThread* t) : t_(t) {
+      t_->cs_.Enter();
+      t_->refcount_ += 1;
+    }
+    ~EnterExit() {
+      bool d = (0 == (--(t_->refcount_)));
+      t_->cs_.Leave();
+      if (d)
+        delete t_;
+    }
+   private:
+    SignalThread* t_;
+  };
+
+  void Run();
+  void OnMainThreadDestroyed();
+
+  Thread* main_;
+  Worker worker_;
+  CriticalSection cs_;
+  State state_;
+  int refcount_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_SIGNALTHREAD_H_
diff --git a/talk/base/sigslot.h b/talk/base/sigslot.h
new file mode 100644
index 0000000..e9b85c7
--- /dev/null
+++ b/talk/base/sigslot.h
@@ -0,0 +1,2816 @@
+// sigslot.h: Signal/Slot classes
+// 
+// Written by Sarah Thompson (sarah@telergy.com) 2002.
+//
+// License: Public domain. You are free to use this code however you like, with the proviso that
+//          the author takes on no responsibility or liability for any use.
+//
+// QUICK DOCUMENTATION 
+//		
+//				(see also the full documentation at http://sigslot.sourceforge.net/)
+//
+//		#define switches
+//			SIGSLOT_PURE_ISO			- Define this to force ISO C++ compliance. This also disables
+//										  all of the thread safety support on platforms where it is 
+//										  available.
+//
+//			SIGSLOT_USE_POSIX_THREADS	- Force use of Posix threads when using a C++ compiler other than
+//										  gcc on a platform that supports Posix threads. (When using gcc,
+//										  this is the default - use SIGSLOT_PURE_ISO to disable this if 
+//										  necessary)
+//
+//			SIGSLOT_DEFAULT_MT_POLICY	- Where thread support is enabled, this defaults to multi_threaded_global.
+//										  Otherwise, the default is single_threaded. #define this yourself to
+//										  override the default. In pure ISO mode, anything other than
+//										  single_threaded will cause a compiler error.
+//
+//		PLATFORM NOTES
+//
+//			Win32						- On Win32, the WIN32 symbol must be #defined. Most mainstream
+//										  compilers do this by default, but you may need to define it
+//										  yourself if your build environment is less standard. This causes
+//										  the Win32 thread support to be compiled in and used automatically.
+//
+//			Unix/Linux/BSD, etc.		- If you're using gcc, it is assumed that you have Posix threads
+//										  available, so they are used automatically. You can override this
+//										  (as under Windows) with the SIGSLOT_PURE_ISO switch. If you're using
+//										  something other than gcc but still want to use Posix threads, you
+//										  need to #define SIGSLOT_USE_POSIX_THREADS.
+//
+//			ISO C++						- If none of the supported platforms are detected, or if
+//										  SIGSLOT_PURE_ISO is defined, all multithreading support is turned off,
+//										  along with any code that might cause a pure ISO C++ environment to
+//										  complain. Before you ask, gcc -ansi -pedantic won't compile this 
+//										  library, but gcc -ansi is fine. Pedantic mode seems to throw a lot of
+//										  errors that aren't really there. If you feel like investigating this,
+//										  please contact the author.
+//
+//		
+//		THREADING MODES
+//
+//			single_threaded				- Your program is assumed to be single threaded from the point of view
+//										  of signal/slot usage (i.e. all objects using signals and slots are
+//										  created and destroyed from a single thread). Behaviour if objects are
+//										  destroyed concurrently is undefined (i.e. you'll get the occasional
+//										  segmentation fault/memory exception).
+//
+//			multi_threaded_global		- Your program is assumed to be multi threaded. Objects using signals and
+//										  slots can be safely created and destroyed from any thread, even when
+//										  connections exist. In multi_threaded_global mode, this is achieved by a
+//										  single global mutex (actually a critical section on Windows because they
+//										  are faster). This option uses less OS resources, but results in more
+//										  opportunities for contention, possibly resulting in more context switches
+//										  than are strictly necessary.
+//
+//			multi_threaded_local		- Behaviour in this mode is essentially the same as multi_threaded_global,
+//										  except that each signal, and each object that inherits has_slots, all 
+//										  have their own mutex/critical section. In practice, this means that
+//										  mutex collisions (and hence context switches) only happen if they are
+//										  absolutely essential. However, on some platforms, creating a lot of 
+//										  mutexes can slow down the whole OS, so use this option with care.
+//
+//		USING THE LIBRARY
+//
+//			See the full documentation at http://sigslot.sourceforge.net/
+//
+//
+
+#ifndef TALK_BASE_SIGSLOT_H__
+#define TALK_BASE_SIGSLOT_H__
+
+#include <set>
+#include <list>
+
+// On our copy of sigslot.h, we force single threading
+#define SIGSLOT_PURE_ISO
+
+#if defined(SIGSLOT_PURE_ISO) || (!defined(WIN32) && !defined(__GNUG__) && !defined(SIGSLOT_USE_POSIX_THREADS))
+#	define _SIGSLOT_SINGLE_THREADED
+#elif defined(WIN32)
+#	define _SIGSLOT_HAS_WIN32_THREADS
+#	include <windows.h>
+#elif defined(__GNUG__) || defined(SIGSLOT_USE_POSIX_THREADS)
+#	define _SIGSLOT_HAS_POSIX_THREADS
+#	include <pthread.h>
+#else
+#	define _SIGSLOT_SINGLE_THREADED
+#endif
+
+#ifndef SIGSLOT_DEFAULT_MT_POLICY
+#	ifdef _SIGSLOT_SINGLE_THREADED
+#		define SIGSLOT_DEFAULT_MT_POLICY single_threaded
+#	else
+#		define SIGSLOT_DEFAULT_MT_POLICY multi_threaded_local
+#	endif
+#endif
+
+// TODO: change this namespace to talk_base?
+namespace sigslot {
+
+	class single_threaded
+	{
+	public:
+		single_threaded()
+		{
+			;
+		}
+
+		virtual ~single_threaded()
+		{
+			;
+		}
+
+		virtual void lock()
+		{
+			;
+		}
+
+		virtual void unlock()
+		{
+			;
+		}
+	};
+
+#ifdef _SIGSLOT_HAS_WIN32_THREADS
+	// The multi threading policies only get compiled in if they are enabled.
+	class multi_threaded_global
+	{
+	public:
+		multi_threaded_global()
+		{
+			static bool isinitialised = false;
+
+			if(!isinitialised)
+			{
+				InitializeCriticalSection(get_critsec());
+				isinitialised = true;
+			}
+		}
+
+		multi_threaded_global(const multi_threaded_global&)
+		{
+			;
+		}
+
+		virtual ~multi_threaded_global()
+		{
+			;
+		}
+
+		virtual void lock()
+		{
+			EnterCriticalSection(get_critsec());
+		}
+
+		virtual void unlock()
+		{
+			LeaveCriticalSection(get_critsec());
+		}
+
+	private:
+		CRITICAL_SECTION* get_critsec()
+		{
+			static CRITICAL_SECTION g_critsec;
+			return &g_critsec;
+		}
+	};
+
+	class multi_threaded_local
+	{
+	public:
+		multi_threaded_local()
+		{
+			InitializeCriticalSection(&m_critsec);
+		}
+
+		multi_threaded_local(const multi_threaded_local&)
+		{
+			InitializeCriticalSection(&m_critsec);
+		}
+
+		virtual ~multi_threaded_local()
+		{
+			DeleteCriticalSection(&m_critsec);
+		}
+
+		virtual void lock()
+		{
+			EnterCriticalSection(&m_critsec);
+		}
+
+		virtual void unlock()
+		{
+			LeaveCriticalSection(&m_critsec);
+		}
+
+	private:
+		CRITICAL_SECTION m_critsec;
+	};
+#endif // _SIGSLOT_HAS_WIN32_THREADS
+
+#ifdef _SIGSLOT_HAS_POSIX_THREADS
+	// The multi threading policies only get compiled in if they are enabled.
+	class multi_threaded_global
+	{
+	public:
+		multi_threaded_global()
+		{
+			pthread_mutex_init(get_mutex(), NULL);
+		}
+
+		multi_threaded_global(const multi_threaded_global&)
+		{
+			;
+		}
+
+		virtual ~multi_threaded_global()
+		{
+			;
+		}
+
+		virtual void lock()
+		{
+			pthread_mutex_lock(get_mutex());
+		}
+
+		virtual void unlock()
+		{
+			pthread_mutex_unlock(get_mutex());
+		}
+
+	private:
+		pthread_mutex_t* get_mutex()
+		{
+			static pthread_mutex_t g_mutex;
+			return &g_mutex;
+		}
+	};
+
+	class multi_threaded_local
+	{
+	public:
+		multi_threaded_local()
+		{
+			pthread_mutex_init(&m_mutex, NULL);
+		}
+
+		multi_threaded_local(const multi_threaded_local&)
+		{
+			pthread_mutex_init(&m_mutex, NULL);
+		}
+
+		virtual ~multi_threaded_local()
+		{
+			pthread_mutex_destroy(&m_mutex);
+		}
+
+		virtual void lock()
+		{
+			pthread_mutex_lock(&m_mutex);
+		}
+
+		virtual void unlock()
+		{
+			pthread_mutex_unlock(&m_mutex);
+		}
+
+	private:
+		pthread_mutex_t m_mutex;
+	};
+#endif // _SIGSLOT_HAS_POSIX_THREADS
+
+	template<class mt_policy>
+	class lock_block
+	{
+	public:
+		mt_policy *m_mutex;
+
+		lock_block(mt_policy *mtx)
+			: m_mutex(mtx)
+		{
+			m_mutex->lock();
+		}
+
+		~lock_block()
+		{
+			m_mutex->unlock();
+		}
+	};
+
+	template<class mt_policy>
+	class has_slots;
+
+	template<class mt_policy>
+	class _connection_base0
+	{
+	public:
+		virtual ~_connection_base0() {}
+		virtual has_slots<mt_policy>* getdest() const = 0;
+		virtual void emit() = 0;
+		virtual _connection_base0* clone() = 0;
+		virtual _connection_base0* duplicate(has_slots<mt_policy>* pnewdest) = 0;
+	};
+
+	template<class arg1_type, class mt_policy>
+	class _connection_base1
+	{
+	public:
+		virtual ~_connection_base1() {}
+		virtual has_slots<mt_policy>* getdest() const = 0;
+		virtual void emit(arg1_type) = 0;
+		virtual _connection_base1<arg1_type, mt_policy>* clone() = 0;
+		virtual _connection_base1<arg1_type, mt_policy>* duplicate(has_slots<mt_policy>* pnewdest) = 0;
+	};
+
+	template<class arg1_type, class arg2_type, class mt_policy>
+	class _connection_base2
+	{
+	public:
+		virtual ~_connection_base2() {}
+		virtual has_slots<mt_policy>* getdest() const = 0;
+		virtual void emit(arg1_type, arg2_type) = 0;
+		virtual _connection_base2<arg1_type, arg2_type, mt_policy>* clone() = 0;
+		virtual _connection_base2<arg1_type, arg2_type, mt_policy>* duplicate(has_slots<mt_policy>* pnewdest) = 0;
+	};
+
+	template<class arg1_type, class arg2_type, class arg3_type, class mt_policy>
+	class _connection_base3
+	{
+	public:
+		virtual ~_connection_base3() {}
+		virtual has_slots<mt_policy>* getdest() const = 0;
+		virtual void emit(arg1_type, arg2_type, arg3_type) = 0;
+		virtual _connection_base3<arg1_type, arg2_type, arg3_type, mt_policy>* clone() = 0;
+		virtual _connection_base3<arg1_type, arg2_type, arg3_type, mt_policy>* duplicate(has_slots<mt_policy>* pnewdest) = 0;
+	};
+
+	template<class arg1_type, class arg2_type, class arg3_type, class arg4_type, class mt_policy>
+	class _connection_base4
+	{
+	public:
+		virtual ~_connection_base4() {}
+		virtual has_slots<mt_policy>* getdest() const = 0;
+		virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type) = 0;
+		virtual _connection_base4<arg1_type, arg2_type, arg3_type, arg4_type, mt_policy>* clone() = 0;
+		virtual _connection_base4<arg1_type, arg2_type, arg3_type, arg4_type, mt_policy>* duplicate(has_slots<mt_policy>* pnewdest) = 0;
+	};
+
+	template<class arg1_type, class arg2_type, class arg3_type, class arg4_type,
+	class arg5_type, class mt_policy>
+	class _connection_base5
+	{
+	public:
+		virtual ~_connection_base5() {}
+		virtual has_slots<mt_policy>* getdest() const = 0;
+		virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type, 
+			arg5_type) = 0;
+		virtual _connection_base5<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, mt_policy>* clone() = 0;
+		virtual _connection_base5<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, mt_policy>* duplicate(has_slots<mt_policy>* pnewdest) = 0;
+	};
+
+	template<class arg1_type, class arg2_type, class arg3_type, class arg4_type,
+	class arg5_type, class arg6_type, class mt_policy>
+	class _connection_base6
+	{
+	public:
+		virtual ~_connection_base6() {}
+		virtual has_slots<mt_policy>* getdest() const = 0;
+		virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type, arg5_type,
+			arg6_type) = 0;
+		virtual _connection_base6<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, mt_policy>* clone() = 0;
+		virtual _connection_base6<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, mt_policy>* duplicate(has_slots<mt_policy>* pnewdest) = 0;
+	};
+
+	template<class arg1_type, class arg2_type, class arg3_type, class arg4_type,
+	class arg5_type, class arg6_type, class arg7_type, class mt_policy>
+	class _connection_base7
+	{
+	public:
+		virtual ~_connection_base7() {}
+		virtual has_slots<mt_policy>* getdest() const = 0;
+		virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type, arg5_type,
+			arg6_type, arg7_type) = 0;
+		virtual _connection_base7<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, arg7_type, mt_policy>* clone() = 0;
+		virtual _connection_base7<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, arg7_type, mt_policy>* duplicate(has_slots<mt_policy>* pnewdest) = 0;
+	};
+
+	template<class arg1_type, class arg2_type, class arg3_type, class arg4_type,
+	class arg5_type, class arg6_type, class arg7_type, class arg8_type, class mt_policy>
+	class _connection_base8
+	{
+	public:
+		virtual ~_connection_base8() {}
+		virtual has_slots<mt_policy>* getdest() const = 0;
+		virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type, arg5_type,
+			arg6_type, arg7_type, arg8_type) = 0;
+		virtual _connection_base8<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, arg7_type, arg8_type, mt_policy>* clone() = 0;
+		virtual _connection_base8<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, arg7_type, arg8_type, mt_policy>* duplicate(has_slots<mt_policy>* pnewdest) = 0;
+	};
+
+	template<class mt_policy>
+	class _signal_base : public mt_policy
+	{
+	public:
+		virtual void slot_disconnect(has_slots<mt_policy>* pslot) = 0;
+		virtual void slot_duplicate(const has_slots<mt_policy>* poldslot, has_slots<mt_policy>* pnewslot) = 0;
+	};
+
+	template<class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+	class has_slots : public mt_policy 
+	{
+	private:
+		typedef typename std::set<_signal_base<mt_policy> *> sender_set;
+		typedef typename sender_set::const_iterator const_iterator;
+
+	public:
+		has_slots()
+		{
+			;
+		}
+
+		has_slots(const has_slots& hs)
+			: mt_policy(hs)
+		{
+			lock_block<mt_policy> lock(this);
+			const_iterator it = hs.m_senders.begin();
+			const_iterator itEnd = hs.m_senders.end();
+
+			while(it != itEnd)
+			{
+				(*it)->slot_duplicate(&hs, this);
+				m_senders.insert(*it);
+				++it;
+			}
+		} 
+
+		void signal_connect(_signal_base<mt_policy>* sender)
+		{
+			lock_block<mt_policy> lock(this);
+			m_senders.insert(sender);
+		}
+
+		void signal_disconnect(_signal_base<mt_policy>* sender)
+		{
+			lock_block<mt_policy> lock(this);
+			m_senders.erase(sender);
+		}
+
+		virtual ~has_slots()
+		{
+			disconnect_all();
+		}
+
+		void disconnect_all()
+		{
+			lock_block<mt_policy> lock(this);
+			const_iterator it = m_senders.begin();
+			const_iterator itEnd = m_senders.end();
+
+			while(it != itEnd)
+			{
+				(*it)->slot_disconnect(this);
+				++it;
+			}
+
+			m_senders.erase(m_senders.begin(), m_senders.end());
+		}
+
+	private:
+		sender_set m_senders;
+	};
+
+	template<class mt_policy>
+	class _signal_base0 : public _signal_base<mt_policy>
+	{
+	public:
+		typedef std::list<_connection_base0<mt_policy> *>  connections_list;
+
+		_signal_base0()
+		{
+			;
+		}
+
+		_signal_base0(const _signal_base0& s)
+			: _signal_base<mt_policy>(s)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = s.m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = s.m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				(*it)->getdest()->signal_connect(this);
+				m_connected_slots.push_back((*it)->clone());
+
+				++it;
+			}
+		}
+
+		~_signal_base0()
+		{
+			disconnect_all();
+		}
+
+		bool is_empty()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+			return it == itEnd;
+		}
+
+		void disconnect_all()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				(*it)->getdest()->signal_disconnect(this);
+				delete *it;
+
+				++it;
+			}
+
+			m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end());
+		}
+
+#ifdef _DEBUG
+			bool connected(has_slots<mt_policy>* pclass)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+				if ((*it)->getdest() == pclass)
+					return true;
+				it = itNext;
+			}
+			return false;
+		}
+#endif
+
+		void disconnect(has_slots<mt_policy>* pclass)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				if((*it)->getdest() == pclass)
+				{
+					delete *it;
+					m_connected_slots.erase(it);
+					pclass->signal_disconnect(this);
+					return;
+				}
+
+				++it;
+			}
+		}
+
+		void slot_disconnect(has_slots<mt_policy>* pslot)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				typename connections_list::iterator itNext = it;
+				++itNext;
+
+				if((*it)->getdest() == pslot)
+				{
+					delete *it;
+					m_connected_slots.erase(it);
+				}
+
+				it = itNext;
+			}
+		}
+
+		void slot_duplicate(const has_slots<mt_policy>* oldtarget, has_slots<mt_policy>* newtarget)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				if((*it)->getdest() == oldtarget)
+				{
+					m_connected_slots.push_back((*it)->duplicate(newtarget));
+				}
+
+				++it;
+			}
+		}
+
+	protected:
+		connections_list m_connected_slots;   
+	};
+
+	template<class arg1_type, class mt_policy>
+	class _signal_base1 : public _signal_base<mt_policy>
+	{
+	public:
+		typedef std::list<_connection_base1<arg1_type, mt_policy> *>  connections_list;
+
+		_signal_base1()
+		{
+			;
+		}
+
+		_signal_base1(const _signal_base1<arg1_type, mt_policy>& s)
+			: _signal_base<mt_policy>(s)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = s.m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = s.m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				(*it)->getdest()->signal_connect(this);
+				m_connected_slots.push_back((*it)->clone());
+
+				++it;
+			}
+		}
+
+		void slot_duplicate(const has_slots<mt_policy>* oldtarget, has_slots<mt_policy>* newtarget)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				if((*it)->getdest() == oldtarget)
+				{
+					m_connected_slots.push_back((*it)->duplicate(newtarget));
+				}
+
+				++it;
+			}
+		}
+
+		~_signal_base1()
+		{
+			disconnect_all();
+		}
+
+		bool is_empty()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+			return it == itEnd;
+		}
+
+		void disconnect_all()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				(*it)->getdest()->signal_disconnect(this);
+				delete *it;
+
+				++it;
+			}
+
+			m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end());
+		}
+
+#ifdef _DEBUG
+			bool connected(has_slots<mt_policy>* pclass)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+				if ((*it)->getdest() == pclass)
+					return true;
+				it = itNext;
+			}
+			return false;
+		}
+#endif
+
+		void disconnect(has_slots<mt_policy>* pclass)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				if((*it)->getdest() == pclass)
+				{
+					delete *it;
+					m_connected_slots.erase(it);
+					pclass->signal_disconnect(this);
+					return;
+				}
+
+				++it;
+			}
+		}
+
+		void slot_disconnect(has_slots<mt_policy>* pslot)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				typename connections_list::iterator itNext = it;
+				++itNext;
+
+				if((*it)->getdest() == pslot)
+				{
+					delete *it;
+					m_connected_slots.erase(it);
+				}
+
+				it = itNext;
+			}
+		}
+
+
+	protected:
+		connections_list m_connected_slots;   
+	};
+
+	template<class arg1_type, class arg2_type, class mt_policy>
+	class _signal_base2 : public _signal_base<mt_policy>
+	{
+	public:
+		typedef std::list<_connection_base2<arg1_type, arg2_type, mt_policy> *>
+			connections_list;
+
+		_signal_base2()
+		{
+			;
+		}
+
+		_signal_base2(const _signal_base2<arg1_type, arg2_type, mt_policy>& s)
+			: _signal_base<mt_policy>(s)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = s.m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = s.m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				(*it)->getdest()->signal_connect(this);
+				m_connected_slots.push_back((*it)->clone());
+
+				++it;
+			}
+		}
+
+		void slot_duplicate(const has_slots<mt_policy>* oldtarget, has_slots<mt_policy>* newtarget)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				if((*it)->getdest() == oldtarget)
+				{
+					m_connected_slots.push_back((*it)->duplicate(newtarget));
+				}
+
+				++it;
+			}
+		}
+
+		~_signal_base2()
+		{
+			disconnect_all();
+		}
+
+		bool is_empty()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+			return it == itEnd;
+		}
+
+		void disconnect_all()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				(*it)->getdest()->signal_disconnect(this);
+				delete *it;
+
+				++it;
+			}
+
+			m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end());
+		}
+
+#ifdef _DEBUG
+			bool connected(has_slots<mt_policy>* pclass)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+				if ((*it)->getdest() == pclass)
+					return true;
+				it = itNext;
+			}
+			return false;
+		}
+#endif
+
+		void disconnect(has_slots<mt_policy>* pclass)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				if((*it)->getdest() == pclass)
+				{
+					delete *it;
+					m_connected_slots.erase(it);
+					pclass->signal_disconnect(this);
+					return;
+				}
+
+				++it;
+			}
+		}
+
+		void slot_disconnect(has_slots<mt_policy>* pslot)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				typename connections_list::iterator itNext = it;
+				++itNext;
+
+				if((*it)->getdest() == pslot)
+				{
+					delete *it;
+					m_connected_slots.erase(it);
+				}
+
+				it = itNext;
+			}
+		}
+
+	protected:
+		connections_list m_connected_slots;   
+	};
+
+	template<class arg1_type, class arg2_type, class arg3_type, class mt_policy>
+	class _signal_base3 : public _signal_base<mt_policy>
+	{
+	public:
+		typedef std::list<_connection_base3<arg1_type, arg2_type, arg3_type, mt_policy> *>
+			connections_list;
+
+		_signal_base3()
+		{
+			;
+		}
+
+		_signal_base3(const _signal_base3<arg1_type, arg2_type, arg3_type, mt_policy>& s)
+			: _signal_base<mt_policy>(s)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = s.m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = s.m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				(*it)->getdest()->signal_connect(this);
+				m_connected_slots.push_back((*it)->clone());
+
+				++it;
+			}
+		}
+
+		void slot_duplicate(const has_slots<mt_policy>* oldtarget, has_slots<mt_policy>* newtarget)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				if((*it)->getdest() == oldtarget)
+				{
+					m_connected_slots.push_back((*it)->duplicate(newtarget));
+				}
+
+				++it;
+			}
+		}
+
+		~_signal_base3()
+		{
+			disconnect_all();
+		}
+
+		bool is_empty()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+			return it == itEnd;
+		}
+
+		void disconnect_all()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				(*it)->getdest()->signal_disconnect(this);
+				delete *it;
+
+				++it;
+			}
+
+			m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end());
+		}
+
+#ifdef _DEBUG
+			bool connected(has_slots<mt_policy>* pclass)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+				if ((*it)->getdest() == pclass)
+					return true;
+				it = itNext;
+			}
+			return false;
+		}
+#endif
+
+		void disconnect(has_slots<mt_policy>* pclass)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				if((*it)->getdest() == pclass)
+				{
+					delete *it;
+					m_connected_slots.erase(it);
+					pclass->signal_disconnect(this);
+					return;
+				}
+
+				++it;
+			}
+		}
+
+		void slot_disconnect(has_slots<mt_policy>* pslot)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				typename connections_list::iterator itNext = it;
+				++itNext;
+
+				if((*it)->getdest() == pslot)
+				{
+					delete *it;
+					m_connected_slots.erase(it);
+				}
+
+				it = itNext;
+			}
+		}
+
+	protected:
+		connections_list m_connected_slots;   
+	};
+
+	template<class arg1_type, class arg2_type, class arg3_type, class arg4_type, class mt_policy>
+	class _signal_base4 : public _signal_base<mt_policy>
+	{
+	public:
+		typedef std::list<_connection_base4<arg1_type, arg2_type, arg3_type,
+			arg4_type, mt_policy> *>  connections_list;
+
+		_signal_base4()
+		{
+			;
+		}
+
+		_signal_base4(const _signal_base4<arg1_type, arg2_type, arg3_type, arg4_type, mt_policy>& s)
+			: _signal_base<mt_policy>(s)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = s.m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = s.m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				(*it)->getdest()->signal_connect(this);
+				m_connected_slots.push_back((*it)->clone());
+
+				++it;
+			}
+		}
+
+		void slot_duplicate(const has_slots<mt_policy>* oldtarget, has_slots<mt_policy>* newtarget)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				if((*it)->getdest() == oldtarget)
+				{
+					m_connected_slots.push_back((*it)->duplicate(newtarget));
+				}
+
+				++it;
+			}
+		}
+
+		~_signal_base4()
+		{
+			disconnect_all();
+		}
+
+		bool is_empty()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+			return it == itEnd;
+		}
+
+		void disconnect_all()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				(*it)->getdest()->signal_disconnect(this);
+				delete *it;
+
+				++it;
+			}
+
+			m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end());
+		}
+
+#ifdef _DEBUG
+			bool connected(has_slots<mt_policy>* pclass)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+				if ((*it)->getdest() == pclass)
+					return true;
+				it = itNext;
+			}
+			return false;
+		}
+#endif
+
+		void disconnect(has_slots<mt_policy>* pclass)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				if((*it)->getdest() == pclass)
+				{
+					delete *it;
+					m_connected_slots.erase(it);
+					pclass->signal_disconnect(this);
+					return;
+				}
+
+				++it;
+			}
+		}
+
+		void slot_disconnect(has_slots<mt_policy>* pslot)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				typename connections_list::iterator itNext = it;
+				++itNext;
+
+				if((*it)->getdest() == pslot)
+				{
+					delete *it;
+					m_connected_slots.erase(it);
+				}
+
+				it = itNext;
+			}
+		}
+
+	protected:
+		connections_list m_connected_slots;   
+	};
+
+	template<class arg1_type, class arg2_type, class arg3_type, class arg4_type,
+	class arg5_type, class mt_policy>
+	class _signal_base5 : public _signal_base<mt_policy>
+	{
+	public:
+		typedef std::list<_connection_base5<arg1_type, arg2_type, arg3_type,
+			arg4_type, arg5_type, mt_policy> *>  connections_list;
+
+		_signal_base5()
+		{
+			;
+		}
+
+		_signal_base5(const _signal_base5<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, mt_policy>& s)
+			: _signal_base<mt_policy>(s)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = s.m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = s.m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				(*it)->getdest()->signal_connect(this);
+				m_connected_slots.push_back((*it)->clone());
+
+				++it;
+			}
+		}
+
+		void slot_duplicate(const has_slots<mt_policy>* oldtarget, has_slots<mt_policy>* newtarget)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				if((*it)->getdest() == oldtarget)
+				{
+					m_connected_slots.push_back((*it)->duplicate(newtarget));
+				}
+
+				++it;
+			}
+		}
+
+		~_signal_base5()
+		{
+			disconnect_all();
+		}
+
+		bool is_empty()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+			return it == itEnd;
+		}
+
+		void disconnect_all()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				(*it)->getdest()->signal_disconnect(this);
+				delete *it;
+
+				++it;
+			}
+
+			m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end());
+		}
+
+#ifdef _DEBUG
+			bool connected(has_slots<mt_policy>* pclass)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+				if ((*it)->getdest() == pclass)
+					return true;
+				it = itNext;
+			}
+			return false;
+		}
+#endif
+
+		void disconnect(has_slots<mt_policy>* pclass)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				if((*it)->getdest() == pclass)
+				{
+					delete *it;
+					m_connected_slots.erase(it);
+					pclass->signal_disconnect(this);
+					return;
+				}
+
+				++it;
+			}
+		}
+
+		void slot_disconnect(has_slots<mt_policy>* pslot)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				typename connections_list::iterator itNext = it;
+				++itNext;
+
+				if((*it)->getdest() == pslot)
+				{
+					delete *it;
+					m_connected_slots.erase(it);
+				}
+
+				it = itNext;
+			}
+		}
+
+	protected:
+		connections_list m_connected_slots;   
+	};
+
+	template<class arg1_type, class arg2_type, class arg3_type, class arg4_type,
+	class arg5_type, class arg6_type, class mt_policy>
+	class _signal_base6 : public _signal_base<mt_policy>
+	{
+	public:
+		typedef std::list<_connection_base6<arg1_type, arg2_type, arg3_type, 
+			arg4_type, arg5_type, arg6_type, mt_policy> *>  connections_list;
+
+		_signal_base6()
+		{
+			;
+		}
+
+		_signal_base6(const _signal_base6<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, mt_policy>& s)
+			: _signal_base<mt_policy>(s)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = s.m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = s.m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				(*it)->getdest()->signal_connect(this);
+				m_connected_slots.push_back((*it)->clone());
+
+				++it;
+			}
+		}
+
+		void slot_duplicate(const has_slots<mt_policy>* oldtarget, has_slots<mt_policy>* newtarget)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				if((*it)->getdest() == oldtarget)
+				{
+					m_connected_slots.push_back((*it)->duplicate(newtarget));
+				}
+
+				++it;
+			}
+		}
+
+		~_signal_base6()
+		{
+			disconnect_all();
+		}
+
+		bool is_empty()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+			return it == itEnd;
+		}
+
+		void disconnect_all()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				(*it)->getdest()->signal_disconnect(this);
+				delete *it;
+
+				++it;
+			}
+
+			m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end());
+		}
+
+#ifdef _DEBUG
+			bool connected(has_slots<mt_policy>* pclass)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+				if ((*it)->getdest() == pclass)
+					return true;
+				it = itNext;
+			}
+			return false;
+		}
+#endif
+
+		void disconnect(has_slots<mt_policy>* pclass)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				if((*it)->getdest() == pclass)
+				{
+					delete *it;
+					m_connected_slots.erase(it);
+					pclass->signal_disconnect(this);
+					return;
+				}
+
+				++it;
+			}
+		}
+
+		void slot_disconnect(has_slots<mt_policy>* pslot)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				typename connections_list::iterator itNext = it;
+				++itNext;
+
+				if((*it)->getdest() == pslot)
+				{
+					delete *it;
+					m_connected_slots.erase(it);
+				}
+
+				it = itNext;
+			}
+		}
+
+	protected:
+		connections_list m_connected_slots;   
+	};
+
+	template<class arg1_type, class arg2_type, class arg3_type, class arg4_type,
+	class arg5_type, class arg6_type, class arg7_type, class mt_policy>
+	class _signal_base7 : public _signal_base<mt_policy>
+	{
+	public:
+		typedef std::list<_connection_base7<arg1_type, arg2_type, arg3_type, 
+			arg4_type, arg5_type, arg6_type, arg7_type, mt_policy> *>  connections_list;
+
+		_signal_base7()
+		{
+			;
+		}
+
+		_signal_base7(const _signal_base7<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, arg7_type, mt_policy>& s)
+			: _signal_base<mt_policy>(s)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = s.m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = s.m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				(*it)->getdest()->signal_connect(this);
+				m_connected_slots.push_back((*it)->clone());
+
+				++it;
+			}
+		}
+
+		void slot_duplicate(const has_slots<mt_policy>* oldtarget, has_slots<mt_policy>* newtarget)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				if((*it)->getdest() == oldtarget)
+				{
+					m_connected_slots.push_back((*it)->duplicate(newtarget));
+				}
+
+				++it;
+			}
+		}
+
+		~_signal_base7()
+		{
+			disconnect_all();
+		}
+
+		bool is_empty()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+			return it == itEnd;
+		}
+
+		void disconnect_all()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				(*it)->getdest()->signal_disconnect(this);
+				delete *it;
+
+				++it;
+			}
+
+			m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end());
+		}
+
+#ifdef _DEBUG
+			bool connected(has_slots<mt_policy>* pclass)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+				if ((*it)->getdest() == pclass)
+					return true;
+				it = itNext;
+			}
+			return false;
+		}
+#endif
+
+		void disconnect(has_slots<mt_policy>* pclass)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				if((*it)->getdest() == pclass)
+				{
+					delete *it;
+					m_connected_slots.erase(it);
+					pclass->signal_disconnect(this);
+					return;
+				}
+
+				++it;
+			}
+		}
+
+		void slot_disconnect(has_slots<mt_policy>* pslot)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				typename connections_list::iterator itNext = it;
+				++itNext;
+
+				if((*it)->getdest() == pslot)
+				{
+					delete *it;
+					m_connected_slots.erase(it);
+				}
+
+				it = itNext;
+			}
+		}
+
+	protected:
+		connections_list m_connected_slots;   
+	};
+
+	template<class arg1_type, class arg2_type, class arg3_type, class arg4_type,
+	class arg5_type, class arg6_type, class arg7_type, class arg8_type, class mt_policy>
+	class _signal_base8 : public _signal_base<mt_policy>
+	{
+	public:
+		typedef std::list<_connection_base8<arg1_type, arg2_type, arg3_type, 
+			arg4_type, arg5_type, arg6_type, arg7_type, arg8_type, mt_policy> *>
+			connections_list;
+
+		_signal_base8()
+		{
+			;
+		}
+
+		_signal_base8(const _signal_base8<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, arg7_type, arg8_type, mt_policy>& s)
+			: _signal_base<mt_policy>(s)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = s.m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = s.m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				(*it)->getdest()->signal_connect(this);
+				m_connected_slots.push_back((*it)->clone());
+
+				++it;
+			}
+		}
+
+		void slot_duplicate(const has_slots<mt_policy>* oldtarget, has_slots<mt_policy>* newtarget)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				if((*it)->getdest() == oldtarget)
+				{
+					m_connected_slots.push_back((*it)->duplicate(newtarget));
+				}
+
+				++it;
+			}
+		}
+
+		~_signal_base8()
+		{
+			disconnect_all();
+		}
+
+		bool is_empty()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+			return it == itEnd;
+		}
+
+		void disconnect_all()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				(*it)->getdest()->signal_disconnect(this);
+				delete *it;
+
+				++it;
+			}
+
+			m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end());
+		}
+
+#ifdef _DEBUG
+			bool connected(has_slots<mt_policy>* pclass)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+				if ((*it)->getdest() == pclass)
+					return true;
+				it = itNext;
+			}
+			return false;
+		}
+#endif
+
+		void disconnect(has_slots<mt_policy>* pclass)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				if((*it)->getdest() == pclass)
+				{
+					delete *it;
+					m_connected_slots.erase(it);
+					pclass->signal_disconnect(this);
+					return;
+				}
+
+				++it;
+			}
+		}
+
+		void slot_disconnect(has_slots<mt_policy>* pslot)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				typename connections_list::iterator itNext = it;
+				++itNext;
+
+				if((*it)->getdest() == pslot)
+				{
+					delete *it;
+					m_connected_slots.erase(it);
+				}
+
+				it = itNext;
+			}
+		}
+
+	protected:
+		connections_list m_connected_slots;   
+	};
+
+
+	template<class dest_type, class mt_policy>
+	class _connection0 : public _connection_base0<mt_policy>
+	{
+	public:
+		_connection0()
+		{
+			m_pobject = NULL;
+			m_pmemfun = NULL;
+		}
+
+		_connection0(dest_type* pobject, void (dest_type::*pmemfun)())
+		{
+			m_pobject = pobject;
+			m_pmemfun = pmemfun;
+		}
+
+		virtual ~_connection0()
+		{
+                }
+
+		virtual _connection_base0<mt_policy>* clone()
+		{
+			return new _connection0<dest_type, mt_policy>(*this);
+		}
+
+		virtual _connection_base0<mt_policy>* duplicate(has_slots<mt_policy>* pnewdest)
+		{
+			return new _connection0<dest_type, mt_policy>((dest_type *)pnewdest, m_pmemfun);
+		}
+
+		virtual void emit()
+		{
+			(m_pobject->*m_pmemfun)();
+		}
+
+		virtual has_slots<mt_policy>* getdest() const
+		{
+			return m_pobject;
+		}
+
+	private:
+		dest_type* m_pobject;
+		void (dest_type::* m_pmemfun)();
+	};
+
+	template<class dest_type, class arg1_type, class mt_policy>
+	class _connection1 : public _connection_base1<arg1_type, mt_policy>
+	{
+	public:
+		_connection1()
+		{
+			m_pobject = NULL;
+			m_pmemfun = NULL;
+		}
+
+		_connection1(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type))
+		{
+			m_pobject = pobject;
+			m_pmemfun = pmemfun;
+		}
+
+		virtual ~_connection1()
+		{
+                }
+
+		virtual _connection_base1<arg1_type, mt_policy>* clone()
+		{
+			return new _connection1<dest_type, arg1_type, mt_policy>(*this);
+		}
+
+		virtual _connection_base1<arg1_type, mt_policy>* duplicate(has_slots<mt_policy>* pnewdest)
+		{
+			return new _connection1<dest_type, arg1_type, mt_policy>((dest_type *)pnewdest, m_pmemfun);
+		}
+
+		virtual void emit(arg1_type a1)
+		{
+			(m_pobject->*m_pmemfun)(a1);
+		}
+
+		virtual has_slots<mt_policy>* getdest() const
+		{
+			return m_pobject;
+		}
+
+	private:
+		dest_type* m_pobject;
+		void (dest_type::* m_pmemfun)(arg1_type);
+	};
+
+	template<class dest_type, class arg1_type, class arg2_type, class mt_policy>
+	class _connection2 : public _connection_base2<arg1_type, arg2_type, mt_policy>
+	{
+	public:
+		_connection2()
+		{
+			m_pobject = NULL;
+			m_pmemfun = NULL;
+		}
+
+		_connection2(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type,
+			arg2_type))
+		{
+			m_pobject = pobject;
+			m_pmemfun = pmemfun;
+		}
+
+		virtual ~_connection2()
+		{
+                }
+
+		virtual _connection_base2<arg1_type, arg2_type, mt_policy>* clone()
+		{
+			return new _connection2<dest_type, arg1_type, arg2_type, mt_policy>(*this);
+		}
+
+		virtual _connection_base2<arg1_type, arg2_type, mt_policy>* duplicate(has_slots<mt_policy>* pnewdest)
+		{
+			return new _connection2<dest_type, arg1_type, arg2_type, mt_policy>((dest_type *)pnewdest, m_pmemfun);
+		}
+
+		virtual void emit(arg1_type a1, arg2_type a2)
+		{
+			(m_pobject->*m_pmemfun)(a1, a2);
+		}
+
+		virtual has_slots<mt_policy>* getdest() const
+		{
+			return m_pobject;
+		}
+
+	private:
+		dest_type* m_pobject;
+		void (dest_type::* m_pmemfun)(arg1_type, arg2_type);
+	};
+
+	template<class dest_type, class arg1_type, class arg2_type, class arg3_type, class mt_policy>
+	class _connection3 : public _connection_base3<arg1_type, arg2_type, arg3_type, mt_policy>
+	{
+	public:
+		_connection3()
+		{
+			m_pobject = NULL;
+			m_pmemfun = NULL;
+		}
+
+		_connection3(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type,
+			arg2_type, arg3_type))
+		{
+			m_pobject = pobject;
+			m_pmemfun = pmemfun;
+		}
+
+		virtual ~_connection3()
+		{
+                }
+
+		virtual _connection_base3<arg1_type, arg2_type, arg3_type, mt_policy>* clone()
+		{
+			return new _connection3<dest_type, arg1_type, arg2_type, arg3_type, mt_policy>(*this);
+		}
+
+		virtual _connection_base3<arg1_type, arg2_type, arg3_type, mt_policy>* duplicate(has_slots<mt_policy>* pnewdest)
+		{
+			return new _connection3<dest_type, arg1_type, arg2_type, arg3_type, mt_policy>((dest_type *)pnewdest, m_pmemfun);
+		}
+
+		virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3)
+		{
+			(m_pobject->*m_pmemfun)(a1, a2, a3);
+		}
+
+		virtual has_slots<mt_policy>* getdest() const
+		{
+			return m_pobject;
+		}
+
+	private:
+		dest_type* m_pobject;
+		void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type);
+	};
+
+	template<class dest_type, class arg1_type, class arg2_type, class arg3_type,
+	class arg4_type, class mt_policy>
+	class _connection4 : public _connection_base4<arg1_type, arg2_type,
+		arg3_type, arg4_type, mt_policy>
+	{
+	public:
+		_connection4()
+		{
+			m_pobject = NULL;
+			m_pmemfun = NULL;
+		}
+
+		_connection4(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type,
+			arg2_type, arg3_type, arg4_type))
+		{
+			m_pobject = pobject;
+			m_pmemfun = pmemfun;
+		}
+
+		virtual ~_connection4()
+		{
+                }
+
+		virtual _connection_base4<arg1_type, arg2_type, arg3_type, arg4_type, mt_policy>* clone()
+		{
+			return new _connection4<dest_type, arg1_type, arg2_type, arg3_type, arg4_type, mt_policy>(*this);
+		}
+
+		virtual _connection_base4<arg1_type, arg2_type, arg3_type, arg4_type, mt_policy>* duplicate(has_slots<mt_policy>* pnewdest)
+		{
+			return new _connection4<dest_type, arg1_type, arg2_type, arg3_type, arg4_type, mt_policy>((dest_type *)pnewdest, m_pmemfun);
+		}
+
+		virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, 
+			arg4_type a4)
+		{
+			(m_pobject->*m_pmemfun)(a1, a2, a3, a4);
+		}
+
+		virtual has_slots<mt_policy>* getdest() const
+		{
+			return m_pobject;
+		}
+
+	private:
+		dest_type* m_pobject;
+		void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type,
+			arg4_type);
+	};
+
+	template<class dest_type, class arg1_type, class arg2_type, class arg3_type,
+	class arg4_type, class arg5_type, class mt_policy>
+	class _connection5 : public _connection_base5<arg1_type, arg2_type,
+		arg3_type, arg4_type, arg5_type, mt_policy>
+	{
+	public:
+		_connection5()
+		{
+			m_pobject = NULL;
+			m_pmemfun = NULL;
+		}
+
+		_connection5(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type,
+			arg2_type, arg3_type, arg4_type, arg5_type))
+		{
+			m_pobject = pobject;
+			m_pmemfun = pmemfun;
+		}
+
+		virtual ~_connection5()
+		{
+                }
+
+		virtual _connection_base5<arg1_type, arg2_type, arg3_type, arg4_type, 
+			arg5_type, mt_policy>* clone()
+		{
+			return new _connection5<dest_type, arg1_type, arg2_type, arg3_type, arg4_type, 
+				arg5_type, mt_policy>(*this);
+		}
+
+		virtual _connection_base5<arg1_type, arg2_type, arg3_type, arg4_type, 
+			arg5_type, mt_policy>* duplicate(has_slots<mt_policy>* pnewdest)
+		{
+			return new _connection5<dest_type, arg1_type, arg2_type, arg3_type, arg4_type, 
+				arg5_type, mt_policy>((dest_type *)pnewdest, m_pmemfun);
+		}
+
+		virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4,
+			arg5_type a5)
+		{
+			(m_pobject->*m_pmemfun)(a1, a2, a3, a4, a5);
+		}
+
+		virtual has_slots<mt_policy>* getdest() const
+		{
+			return m_pobject;
+		}
+
+	private:
+		dest_type* m_pobject;
+		void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type);
+	};
+
+	template<class dest_type, class arg1_type, class arg2_type, class arg3_type,
+	class arg4_type, class arg5_type, class arg6_type, class mt_policy>
+	class _connection6 : public _connection_base6<arg1_type, arg2_type,
+		arg3_type, arg4_type, arg5_type, arg6_type, mt_policy>
+	{
+	public:
+		_connection6()
+		{
+			m_pobject = NULL;
+			m_pmemfun = NULL;
+		}
+
+		_connection6(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type,
+			arg2_type, arg3_type, arg4_type, arg5_type, arg6_type))
+		{
+			m_pobject = pobject;
+			m_pmemfun = pmemfun;
+		}
+
+		virtual ~_connection6()
+		{
+                }
+
+		virtual _connection_base6<arg1_type, arg2_type, arg3_type, arg4_type, 
+			arg5_type, arg6_type, mt_policy>* clone()
+		{
+			return new _connection6<dest_type, arg1_type, arg2_type, arg3_type, arg4_type, 
+				arg5_type, arg6_type, mt_policy>(*this);
+		}
+
+		virtual _connection_base6<arg1_type, arg2_type, arg3_type, arg4_type, 
+			arg5_type, arg6_type, mt_policy>* duplicate(has_slots<mt_policy>* pnewdest)
+		{
+			return new _connection6<dest_type, arg1_type, arg2_type, arg3_type, arg4_type, 
+				arg5_type, arg6_type, mt_policy>((dest_type *)pnewdest, m_pmemfun);
+		}
+
+		virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4,
+			arg5_type a5, arg6_type a6)
+		{
+			(m_pobject->*m_pmemfun)(a1, a2, a3, a4, a5, a6);
+		}
+
+		virtual has_slots<mt_policy>* getdest() const
+		{
+			return m_pobject;
+		}
+
+	private:
+		dest_type* m_pobject;
+		void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type);
+	};
+
+	template<class dest_type, class arg1_type, class arg2_type, class arg3_type,
+	class arg4_type, class arg5_type, class arg6_type, class arg7_type, class mt_policy>
+	class _connection7 : public _connection_base7<arg1_type, arg2_type,
+		arg3_type, arg4_type, arg5_type, arg6_type, arg7_type, mt_policy>
+	{
+	public:
+		_connection7()
+		{
+			m_pobject = NULL;
+			m_pmemfun = NULL;
+		}
+
+		_connection7(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type,
+			arg2_type, arg3_type, arg4_type, arg5_type, arg6_type, arg7_type))
+		{
+			m_pobject = pobject;
+			m_pmemfun = pmemfun;
+		}
+
+		virtual ~_connection7()
+		{
+                }
+
+		virtual _connection_base7<arg1_type, arg2_type, arg3_type, arg4_type, 
+			arg5_type, arg6_type, arg7_type, mt_policy>* clone()
+		{
+			return new _connection7<dest_type, arg1_type, arg2_type, arg3_type, arg4_type, 
+				arg5_type, arg6_type, arg7_type, mt_policy>(*this);
+		}
+
+		virtual _connection_base7<arg1_type, arg2_type, arg3_type, arg4_type, 
+			arg5_type, arg6_type, arg7_type, mt_policy>* duplicate(has_slots<mt_policy>* pnewdest)
+		{
+			return new _connection7<dest_type, arg1_type, arg2_type, arg3_type, arg4_type, 
+				arg5_type, arg6_type, arg7_type, mt_policy>((dest_type *)pnewdest, m_pmemfun);
+		}
+
+		virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4,
+			arg5_type a5, arg6_type a6, arg7_type a7)
+		{
+			(m_pobject->*m_pmemfun)(a1, a2, a3, a4, a5, a6, a7);
+		}
+
+		virtual has_slots<mt_policy>* getdest() const
+		{
+			return m_pobject;
+		}
+
+	private:
+		dest_type* m_pobject;
+		void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, arg7_type);
+	};
+
+	template<class dest_type, class arg1_type, class arg2_type, class arg3_type,
+	class arg4_type, class arg5_type, class arg6_type, class arg7_type, 
+	class arg8_type, class mt_policy>
+	class _connection8 : public _connection_base8<arg1_type, arg2_type,
+		arg3_type, arg4_type, arg5_type, arg6_type, arg7_type, arg8_type, mt_policy>
+	{
+	public:
+		_connection8()
+		{
+			m_pobject = NULL;
+			m_pmemfun = NULL;
+		}
+
+		_connection8(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type,
+			arg2_type, arg3_type, arg4_type, arg5_type, arg6_type, 
+			arg7_type, arg8_type))
+		{
+			m_pobject = pobject;
+			m_pmemfun = pmemfun;
+		}
+
+		virtual ~_connection8()
+		{
+                }
+
+		virtual _connection_base8<arg1_type, arg2_type, arg3_type, arg4_type, 
+			arg5_type, arg6_type, arg7_type, arg8_type, mt_policy>* clone()
+		{
+			return new _connection8<dest_type, arg1_type, arg2_type, arg3_type, arg4_type, 
+				arg5_type, arg6_type, arg7_type, arg8_type, mt_policy>(*this);
+		}
+
+		virtual _connection_base8<arg1_type, arg2_type, arg3_type, arg4_type, 
+			arg5_type, arg6_type, arg7_type, arg8_type, mt_policy>* duplicate(has_slots<mt_policy>* pnewdest)
+		{
+			return new _connection8<dest_type, arg1_type, arg2_type, arg3_type, arg4_type, 
+				arg5_type, arg6_type, arg7_type, arg8_type, mt_policy>((dest_type *)pnewdest, m_pmemfun);
+		}
+
+		virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4,
+			arg5_type a5, arg6_type a6, arg7_type a7, arg8_type a8)
+		{
+			(m_pobject->*m_pmemfun)(a1, a2, a3, a4, a5, a6, a7, a8);
+		}
+
+		virtual has_slots<mt_policy>* getdest() const
+		{
+			return m_pobject;
+		}
+
+	private:
+		dest_type* m_pobject;
+		void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, arg7_type, arg8_type);
+	};
+
+	template<class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+	class signal0 : public _signal_base0<mt_policy>
+	{
+	public:
+		typedef _signal_base0<mt_policy> base;
+		typedef typename base::connections_list connections_list;
+		using base::m_connected_slots;
+
+		signal0()
+		{
+			;
+		}
+
+		signal0(const signal0<mt_policy>& s)
+			: _signal_base0<mt_policy>(s)
+		{
+			;
+		}
+
+		template<class desttype>
+			void connect(desttype* pclass, void (desttype::*pmemfun)())
+		{
+			lock_block<mt_policy> lock(this);
+			_connection0<desttype, mt_policy>* conn = 
+				new _connection0<desttype, mt_policy>(pclass, pmemfun);
+			m_connected_slots.push_back(conn);
+			pclass->signal_connect(this);
+		}
+
+		void emit()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+
+				(*it)->emit();
+
+				it = itNext;
+			}
+		}
+
+		void operator()()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+
+				(*it)->emit();
+
+				it = itNext;
+			}
+		}
+	};
+
+	template<class arg1_type, class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+	class signal1 : public _signal_base1<arg1_type, mt_policy>
+	{
+	public:
+		typedef _signal_base1<arg1_type, mt_policy> base;
+		typedef typename base::connections_list connections_list;
+		using base::m_connected_slots;
+
+		signal1()
+		{
+			;
+		}
+
+		signal1(const signal1<arg1_type, mt_policy>& s)
+			: _signal_base1<arg1_type, mt_policy>(s)
+		{
+			;
+		}
+
+		template<class desttype>
+			void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type))
+		{
+			lock_block<mt_policy> lock(this);
+			_connection1<desttype, arg1_type, mt_policy>* conn = 
+				new _connection1<desttype, arg1_type, mt_policy>(pclass, pmemfun);
+			m_connected_slots.push_back(conn);
+			pclass->signal_connect(this);
+		}
+
+		void emit(arg1_type a1)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+
+				(*it)->emit(a1);
+
+				it = itNext;
+			}
+		}
+
+		void operator()(arg1_type a1)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+
+				(*it)->emit(a1);
+
+				it = itNext;
+			}
+		}
+	};
+
+	template<class arg1_type, class arg2_type, class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+	class signal2 : public _signal_base2<arg1_type, arg2_type, mt_policy>
+	{
+	public:
+		typedef _signal_base2<arg1_type, arg2_type, mt_policy> base;
+		typedef typename base::connections_list connections_list;
+		using base::m_connected_slots;
+
+		signal2()
+		{
+			;
+		}
+
+		signal2(const signal2<arg1_type, arg2_type, mt_policy>& s)
+			: _signal_base2<arg1_type, arg2_type, mt_policy>(s)
+		{
+			;
+		}
+
+		template<class desttype>
+			void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type,
+			arg2_type))
+		{
+			lock_block<mt_policy> lock(this);
+			_connection2<desttype, arg1_type, arg2_type, mt_policy>* conn = new
+				_connection2<desttype, arg1_type, arg2_type, mt_policy>(pclass, pmemfun);
+			m_connected_slots.push_back(conn);
+			pclass->signal_connect(this);
+		}
+
+		void emit(arg1_type a1, arg2_type a2)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+
+				(*it)->emit(a1, a2);
+
+				it = itNext;
+			}
+		}
+
+		void operator()(arg1_type a1, arg2_type a2)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+
+				(*it)->emit(a1, a2);
+
+				it = itNext;
+			}
+		}
+	};
+
+	template<class arg1_type, class arg2_type, class arg3_type, class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+	class signal3 : public _signal_base3<arg1_type, arg2_type, arg3_type, mt_policy>
+	{
+	public:
+		typedef _signal_base3<arg1_type, arg2_type, arg3_type, mt_policy> base;
+		typedef typename base::connections_list connections_list;
+		using base::m_connected_slots;
+
+		signal3()
+		{
+			;
+		}
+
+		signal3(const signal3<arg1_type, arg2_type, arg3_type, mt_policy>& s)
+			: _signal_base3<arg1_type, arg2_type, arg3_type, mt_policy>(s)
+		{
+			;
+		}
+
+		template<class desttype>
+			void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type,
+			arg2_type, arg3_type))
+		{
+			lock_block<mt_policy> lock(this);
+			_connection3<desttype, arg1_type, arg2_type, arg3_type, mt_policy>* conn = 
+				new _connection3<desttype, arg1_type, arg2_type, arg3_type, mt_policy>(pclass,
+				pmemfun);
+			m_connected_slots.push_back(conn);
+			pclass->signal_connect(this);
+		}
+
+		void emit(arg1_type a1, arg2_type a2, arg3_type a3)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+
+				(*it)->emit(a1, a2, a3);
+
+				it = itNext;
+			}
+		}
+
+		void operator()(arg1_type a1, arg2_type a2, arg3_type a3)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+
+				(*it)->emit(a1, a2, a3);
+
+				it = itNext;
+			}
+		}
+	};
+
+	template<class arg1_type, class arg2_type, class arg3_type, class arg4_type, class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+	class signal4 : public _signal_base4<arg1_type, arg2_type, arg3_type,
+		arg4_type, mt_policy>
+	{
+	public:
+		typedef _signal_base4<arg1_type, arg2_type, arg3_type, arg4_type, mt_policy> base;
+		typedef typename base::connections_list connections_list;
+		using base::m_connected_slots;
+
+		signal4()
+		{
+			;
+		}
+
+		signal4(const signal4<arg1_type, arg2_type, arg3_type, arg4_type, mt_policy>& s)
+			: _signal_base4<arg1_type, arg2_type, arg3_type, arg4_type, mt_policy>(s)
+		{
+			;
+		}
+
+		template<class desttype>
+			void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type,
+			arg2_type, arg3_type, arg4_type))
+		{
+			lock_block<mt_policy> lock(this);
+			_connection4<desttype, arg1_type, arg2_type, arg3_type, arg4_type, mt_policy>*
+				conn = new _connection4<desttype, arg1_type, arg2_type, arg3_type,
+				arg4_type, mt_policy>(pclass, pmemfun);
+			m_connected_slots.push_back(conn);
+			pclass->signal_connect(this);
+		}
+
+		void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+
+				(*it)->emit(a1, a2, a3, a4);
+
+				it = itNext;
+			}
+		}
+
+		void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+
+				(*it)->emit(a1, a2, a3, a4);
+
+				it = itNext;
+			}
+		}
+	};
+
+	template<class arg1_type, class arg2_type, class arg3_type, class arg4_type,
+	class arg5_type, class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+	class signal5 : public _signal_base5<arg1_type, arg2_type, arg3_type,
+		arg4_type, arg5_type, mt_policy>
+	{
+	public:
+		typedef _signal_base5<arg1_type, arg2_type, arg3_type, arg4_type, arg5_type, mt_policy> base;
+		typedef typename base::connections_list connections_list;
+		using base::m_connected_slots;
+
+		signal5()
+		{
+			;
+		}
+
+		signal5(const signal5<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, mt_policy>& s)
+			: _signal_base5<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, mt_policy>(s)
+		{
+			;
+		}
+
+		template<class desttype>
+			void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type,
+			arg2_type, arg3_type, arg4_type, arg5_type))
+		{
+			lock_block<mt_policy> lock(this);
+			_connection5<desttype, arg1_type, arg2_type, arg3_type, arg4_type,
+				arg5_type, mt_policy>* conn = new _connection5<desttype, arg1_type, arg2_type,
+				arg3_type, arg4_type, arg5_type, mt_policy>(pclass, pmemfun);
+			m_connected_slots.push_back(conn);
+			pclass->signal_connect(this);
+		}
+
+		void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4,
+			arg5_type a5)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+
+				(*it)->emit(a1, a2, a3, a4, a5);
+
+				it = itNext;
+			}
+		}
+
+		void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4,
+			arg5_type a5)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+
+				(*it)->emit(a1, a2, a3, a4, a5);
+
+				it = itNext;
+			}
+		}
+	};
+
+
+	template<class arg1_type, class arg2_type, class arg3_type, class arg4_type,
+	class arg5_type, class arg6_type, class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+	class signal6 : public _signal_base6<arg1_type, arg2_type, arg3_type,
+		arg4_type, arg5_type, arg6_type, mt_policy>
+	{
+	public:
+		typedef _signal_base6<arg1_type, arg2_type, arg3_type, arg4_type, arg5_type, arg6_type, mt_policy> base;
+		typedef typename base::connections_list connections_list;
+		using base::m_connected_slots;
+
+		signal6()
+		{
+			;
+		}
+
+		signal6(const signal6<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, mt_policy>& s)
+			: _signal_base6<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, mt_policy>(s)
+		{
+			;
+		}
+
+		template<class desttype>
+			void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type,
+			arg2_type, arg3_type, arg4_type, arg5_type, arg6_type))
+		{
+			lock_block<mt_policy> lock(this);
+			_connection6<desttype, arg1_type, arg2_type, arg3_type, arg4_type,
+				arg5_type, arg6_type, mt_policy>* conn = 
+				new _connection6<desttype, arg1_type, arg2_type, arg3_type,
+				arg4_type, arg5_type, arg6_type, mt_policy>(pclass, pmemfun);
+			m_connected_slots.push_back(conn);
+			pclass->signal_connect(this);
+		}
+
+		void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4,
+			arg5_type a5, arg6_type a6)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+
+				(*it)->emit(a1, a2, a3, a4, a5, a6);
+
+				it = itNext;
+			}
+		}
+
+		void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4,
+			arg5_type a5, arg6_type a6)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+
+				(*it)->emit(a1, a2, a3, a4, a5, a6);
+
+				it = itNext;
+			}
+		}
+	};
+
+	template<class arg1_type, class arg2_type, class arg3_type, class arg4_type,
+	class arg5_type, class arg6_type, class arg7_type, class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+	class signal7 : public _signal_base7<arg1_type, arg2_type, arg3_type,
+		arg4_type, arg5_type, arg6_type, arg7_type, mt_policy>
+	{
+	public:
+		typedef _signal_base7<arg1_type, arg2_type, arg3_type, arg4_type, 
+			arg5_type, arg6_type, arg7_type, mt_policy> base;
+		typedef typename base::connections_list connections_list;
+		using base::m_connected_slots;
+
+		signal7()
+		{
+			;
+		}
+
+		signal7(const signal7<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, arg7_type, mt_policy>& s)
+			: _signal_base7<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, arg7_type, mt_policy>(s)
+		{
+			;
+		}
+
+		template<class desttype>
+			void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type,
+			arg2_type, arg3_type, arg4_type, arg5_type, arg6_type, 
+			arg7_type))
+		{
+			lock_block<mt_policy> lock(this);
+			_connection7<desttype, arg1_type, arg2_type, arg3_type, arg4_type,
+				arg5_type, arg6_type, arg7_type, mt_policy>* conn = 
+				new _connection7<desttype, arg1_type, arg2_type, arg3_type,
+				arg4_type, arg5_type, arg6_type, arg7_type, mt_policy>(pclass, pmemfun);
+			m_connected_slots.push_back(conn);
+			pclass->signal_connect(this);
+		}
+
+		void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4,
+			arg5_type a5, arg6_type a6, arg7_type a7)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+
+				(*it)->emit(a1, a2, a3, a4, a5, a6, a7);
+
+				it = itNext;
+			}
+		}
+
+		void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4,
+			arg5_type a5, arg6_type a6, arg7_type a7)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+
+				(*it)->emit(a1, a2, a3, a4, a5, a6, a7);
+
+				it = itNext;
+			}
+		}
+	};
+
+	template<class arg1_type, class arg2_type, class arg3_type, class arg4_type,
+	class arg5_type, class arg6_type, class arg7_type, class arg8_type, class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+	class signal8 : public _signal_base8<arg1_type, arg2_type, arg3_type,
+		arg4_type, arg5_type, arg6_type, arg7_type, arg8_type, mt_policy>
+	{
+	public:
+		typedef _signal_base8<arg1_type, arg2_type, arg3_type, arg4_type, 
+			arg5_type, arg6_type, arg7_type, arg8_type, mt_policy> base;
+		typedef typename base::connections_list connections_list;
+		using base::m_connected_slots;
+
+		signal8()
+		{
+			;
+		}
+
+		signal8(const signal8<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, arg7_type, arg8_type, mt_policy>& s)
+			: _signal_base8<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, arg7_type, arg8_type, mt_policy>(s)
+		{
+			;
+		}
+
+		template<class desttype>
+			void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type,
+			arg2_type, arg3_type, arg4_type, arg5_type, arg6_type, 
+			arg7_type, arg8_type))
+		{
+			lock_block<mt_policy> lock(this);
+			_connection8<desttype, arg1_type, arg2_type, arg3_type, arg4_type,
+				arg5_type, arg6_type, arg7_type, arg8_type, mt_policy>* conn = 
+				new _connection8<desttype, arg1_type, arg2_type, arg3_type,
+				arg4_type, arg5_type, arg6_type, arg7_type, 
+				arg8_type, mt_policy>(pclass, pmemfun);
+			m_connected_slots.push_back(conn);
+			pclass->signal_connect(this);
+		}
+
+		void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4,
+			arg5_type a5, arg6_type a6, arg7_type a7, arg8_type a8)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+
+				(*it)->emit(a1, a2, a3, a4, a5, a6, a7, a8);
+
+				it = itNext;
+			}
+		}
+
+		void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4,
+			arg5_type a5, arg6_type a6, arg7_type a7, arg8_type a8)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+
+				(*it)->emit(a1, a2, a3, a4, a5, a6, a7, a8);
+
+				it = itNext;
+			}
+		}
+	};
+
+}; // namespace sigslot
+
+#endif // TALK_BASE_SIGSLOT_H__
diff --git a/talk/base/sigslotrepeater.h b/talk/base/sigslotrepeater.h
new file mode 100644
index 0000000..3bcdc95
--- /dev/null
+++ b/talk/base/sigslotrepeater.h
@@ -0,0 +1,107 @@
+/*
+ * libjingle
+ * Copyright 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_BASE_SIGSLOTREPEATER_H__
+#define TALK_BASE_SIGSLOTREPEATER_H__
+
+// repeaters are both signals and slots, which are designed as intermediate
+// pass-throughs for signals and slots which don't know about each other (for
+// modularity or encapsulation).  This eliminates the need to declare a signal
+// handler whose sole purpose is to fire another signal.  The repeater connects
+// to the originating signal using the 'repeat' method.  When the repeated
+// signal fires, the repeater will also fire.
+
+#include "talk/base/sigslot.h"
+
+namespace sigslot {
+
+  template<class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+  class repeater0 : public signal0<mt_policy>,
+                    public has_slots<mt_policy>
+  {
+  public:
+    typedef signal0<mt_policy> base_type;
+    typedef repeater0<mt_policy> this_type;
+
+    repeater0() { }
+    repeater0(const this_type& s) : base_type(s) { }
+
+    void reemit() { signal0<mt_policy>::emit(); }
+    void repeat(base_type &s) { s.connect(this, &this_type::reemit); }
+  };
+
+  template<class arg1_type, class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+  class repeater1 : public signal1<arg1_type, mt_policy>,
+                    public has_slots<mt_policy>
+  {
+  public:
+    typedef signal1<arg1_type, mt_policy> base_type;
+    typedef repeater1<arg1_type, mt_policy> this_type;
+
+    repeater1() { }
+    repeater1(const this_type& s) : base_type(s) { }
+
+    void reemit(arg1_type a1) { signal1<arg1_type, mt_policy>::emit(a1); }
+    void repeat(base_type& s) { s.connect(this, &this_type::reemit); }
+  };
+
+  template<class arg1_type, class arg2_type, class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+  class repeater2 : public signal2<arg1_type, arg2_type, mt_policy>,
+                    public has_slots<mt_policy>
+  {
+  public:
+    typedef signal2<arg1_type, arg2_type, mt_policy> base_type;
+    typedef repeater2<arg1_type, arg2_type, mt_policy> this_type;
+
+    repeater2() { }
+    repeater2(const this_type& s) : base_type(s) { }
+
+    void reemit(arg1_type a1, arg2_type a2) { signal2<arg1_type, arg2_type, mt_policy>::emit(a1,a2); }
+    void repeat(base_type& s) { s.connect(this, &this_type::reemit); }
+  };
+
+  template<class arg1_type, class arg2_type, class arg3_type, 
+           class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+  class repeater3 : public signal3<arg1_type, arg2_type, arg3_type, mt_policy>,
+                    public has_slots<mt_policy>
+  {
+  public:
+    typedef signal3<arg1_type, arg2_type, arg3_type, mt_policy> base_type;
+    typedef repeater3<arg1_type, arg2_type, arg3_type, mt_policy> this_type;
+
+    repeater3() { }
+    repeater3(const this_type& s) : base_type(s) { }
+
+    void reemit(arg1_type a1, arg2_type a2, arg3_type a3) { 
+            signal3<arg1_type, arg2_type, arg3_type, mt_policy>::emit(a1,a2,a3);
+    }
+    void repeat(base_type& s) { s.connect(this, &this_type::reemit); }
+  };
+
+}  // namespace sigslot
+
+#endif  // TALK_BASE_SIGSLOTREPEATER_H__
diff --git a/talk/base/socket.h b/talk/base/socket.h
new file mode 100644
index 0000000..a55b3dc
--- /dev/null
+++ b/talk/base/socket.h
@@ -0,0 +1,199 @@
+/*
+ * 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_BASE_SOCKET_H__
+#define TALK_BASE_SOCKET_H__
+
+#include <errno.h>
+
+#ifdef POSIX
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#define SOCKET_EACCES EACCES
+#endif
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#endif
+
+#include "talk/base/basictypes.h"
+#include "talk/base/socketaddress.h"
+
+// Rather than converting errors into a private namespace,
+// Reuse the POSIX socket api errors. Note this depends on
+// Win32 compatibility.
+
+#ifdef WIN32
+#undef EWOULDBLOCK  // Remove errno.h's definition for each macro below.
+#define EWOULDBLOCK WSAEWOULDBLOCK
+#undef EINPROGRESS
+#define EINPROGRESS WSAEINPROGRESS
+#undef EALREADY
+#define EALREADY WSAEALREADY
+#undef ENOTSOCK
+#define ENOTSOCK WSAENOTSOCK
+#undef EDESTADDRREQ
+#define EDESTADDRREQ WSAEDESTADDRREQ
+#undef EMSGSIZE
+#define EMSGSIZE WSAEMSGSIZE
+#undef EPROTOTYPE
+#define EPROTOTYPE WSAEPROTOTYPE
+#undef ENOPROTOOPT
+#define ENOPROTOOPT WSAENOPROTOOPT
+#undef EPROTONOSUPPORT
+#define EPROTONOSUPPORT WSAEPROTONOSUPPORT
+#undef ESOCKTNOSUPPORT
+#define ESOCKTNOSUPPORT WSAESOCKTNOSUPPORT
+#undef EOPNOTSUPP
+#define EOPNOTSUPP WSAEOPNOTSUPP
+#undef EPFNOSUPPORT
+#define EPFNOSUPPORT WSAEPFNOSUPPORT
+#undef EAFNOSUPPORT
+#define EAFNOSUPPORT WSAEAFNOSUPPORT
+#undef EADDRINUSE
+#define EADDRINUSE WSAEADDRINUSE
+#undef EADDRNOTAVAIL
+#define EADDRNOTAVAIL WSAEADDRNOTAVAIL
+#undef ENETDOWN
+#define ENETDOWN WSAENETDOWN
+#undef ENETUNREACH
+#define ENETUNREACH WSAENETUNREACH
+#undef ENETRESET
+#define ENETRESET WSAENETRESET
+#undef ECONNABORTED
+#define ECONNABORTED WSAECONNABORTED
+#undef ECONNRESET
+#define ECONNRESET WSAECONNRESET
+#undef ENOBUFS
+#define ENOBUFS WSAENOBUFS
+#undef EISCONN
+#define EISCONN WSAEISCONN
+#undef ENOTCONN
+#define ENOTCONN WSAENOTCONN
+#undef ESHUTDOWN
+#define ESHUTDOWN WSAESHUTDOWN
+#undef ETOOMANYREFS
+#define ETOOMANYREFS WSAETOOMANYREFS
+#undef ETIMEDOUT
+#define ETIMEDOUT WSAETIMEDOUT
+#undef ECONNREFUSED
+#define ECONNREFUSED WSAECONNREFUSED
+#undef ELOOP
+#define ELOOP WSAELOOP
+#undef ENAMETOOLONG
+#define ENAMETOOLONG WSAENAMETOOLONG
+#undef EHOSTDOWN
+#define EHOSTDOWN WSAEHOSTDOWN
+#undef EHOSTUNREACH
+#define EHOSTUNREACH WSAEHOSTUNREACH
+#undef ENOTEMPTY
+#define ENOTEMPTY WSAENOTEMPTY
+#undef EPROCLIM
+#define EPROCLIM WSAEPROCLIM
+#undef EUSERS
+#define EUSERS WSAEUSERS
+#undef EDQUOT
+#define EDQUOT WSAEDQUOT
+#undef ESTALE
+#define ESTALE WSAESTALE
+#undef EREMOTE
+#define EREMOTE WSAEREMOTE
+#undef EACCES
+#define SOCKET_EACCES WSAEACCES
+#endif  // WIN32
+
+#ifdef POSIX
+#define INVALID_SOCKET (-1)
+#define SOCKET_ERROR (-1)
+#define closesocket(s) close(s)
+#endif  // POSIX
+
+namespace talk_base {
+
+inline bool IsBlockingError(int e) {
+  return (e == EWOULDBLOCK) || (e == EAGAIN) || (e == EINPROGRESS);
+}
+
+// General interface for the socket implementations of various networks.  The
+// methods match those of normal UNIX sockets very closely.
+class Socket {
+ public:
+  virtual ~Socket() {}
+
+  // Returns the address to which the socket is bound.  If the socket is not
+  // bound, then the any-address is returned.
+  virtual SocketAddress GetLocalAddress() const = 0;
+
+  // Returns the address to which the socket is connected.  If the socket is
+  // not connected, then the any-address is returned.
+  virtual SocketAddress GetRemoteAddress() const = 0;
+
+  virtual int Bind(const SocketAddress& addr) = 0;
+  virtual int Connect(const SocketAddress& addr) = 0;
+  virtual int Send(const void *pv, size_t cb) = 0;
+  virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr) = 0;
+  virtual int Recv(void *pv, size_t cb) = 0;
+  virtual int RecvFrom(void *pv, size_t cb, SocketAddress *paddr) = 0;
+  virtual int Listen(int backlog) = 0;
+  virtual Socket *Accept(SocketAddress *paddr) = 0;
+  virtual int Close() = 0;
+  virtual int GetError() const = 0;
+  virtual void SetError(int error) = 0;
+  inline bool IsBlocking() const { return IsBlockingError(GetError()); }
+
+  enum ConnState {
+    CS_CLOSED,
+    CS_CONNECTING,
+    CS_CONNECTED
+  };
+  virtual ConnState GetState() const = 0;
+
+  // Fills in the given uint16 with the current estimate of the MTU along the
+  // path to the address to which this socket is connected.
+  virtual int EstimateMTU(uint16* mtu) = 0;
+
+  enum Option {
+    OPT_DONTFRAGMENT,
+    OPT_RCVBUF,  // receive buffer size
+    OPT_SNDBUF,  // send buffer size
+    OPT_NODELAY  // whether Nagle algorithm is enabled
+  };
+  virtual int GetOption(Option opt, int* value) = 0;
+  virtual int SetOption(Option opt, int value) = 0;
+
+ protected:
+  Socket() {}
+
+ private:
+  DISALLOW_EVIL_CONSTRUCTORS(Socket);
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_SOCKET_H__
diff --git a/talk/base/socketadapters.cc b/talk/base/socketadapters.cc
new file mode 100644
index 0000000..427ebdc
--- /dev/null
+++ b/talk/base/socketadapters.cc
@@ -0,0 +1,910 @@
+/*
+ * 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.
+ */
+
+#if defined(_MSC_VER) && _MSC_VER < 1300
+#pragma warning(disable:4786)
+#endif
+
+#include <time.h>
+#include <errno.h>
+
+#ifdef WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#define SECURITY_WIN32
+#include <security.h>
+#endif
+
+#include "talk/base/bytebuffer.h"
+#include "talk/base/common.h"
+#include "talk/base/httpcommon.h"
+#include "talk/base/logging.h"
+#include "talk/base/socketadapters.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
+
+#ifdef WIN32
+#include "talk/base/sec_buffer.h"
+#endif  // WIN32
+
+namespace talk_base {
+
+BufferedReadAdapter::BufferedReadAdapter(AsyncSocket* socket, size_t size)
+    : AsyncSocketAdapter(socket), buffer_size_(size),
+      data_len_(0), buffering_(false) {
+  buffer_ = new char[buffer_size_];
+}
+
+BufferedReadAdapter::~BufferedReadAdapter() {
+  delete [] buffer_;
+}
+
+int BufferedReadAdapter::Send(const void *pv, size_t cb) {
+  if (buffering_) {
+    // TODO: Spoof error better; Signal Writeable
+    socket_->SetError(EWOULDBLOCK);
+    return -1;
+  }
+  return AsyncSocketAdapter::Send(pv, cb);
+}
+
+int BufferedReadAdapter::Recv(void *pv, size_t cb) {
+  if (buffering_) {
+    socket_->SetError(EWOULDBLOCK);
+    return -1;
+  }
+
+  size_t read = 0;
+
+  if (data_len_) {
+    read = _min(cb, data_len_);
+    memcpy(pv, buffer_, read);
+    data_len_ -= read;
+    if (data_len_ > 0) {
+      memmove(buffer_, buffer_ + read, data_len_);
+    }
+    pv = static_cast<char *>(pv) + read;
+    cb -= read;
+  }
+
+  // FIX: If cb == 0, we won't generate another read event
+
+  int res = AsyncSocketAdapter::Recv(pv, cb);
+  if (res < 0)
+    return res;
+
+  return res + static_cast<int>(read);
+}
+
+void BufferedReadAdapter::BufferInput(bool on) {
+  buffering_ = on;
+}
+
+void BufferedReadAdapter::OnReadEvent(AsyncSocket * socket) {
+  ASSERT(socket == socket_);
+
+  if (!buffering_) {
+    AsyncSocketAdapter::OnReadEvent(socket);
+    return;
+  }
+
+  if (data_len_ >= buffer_size_) {
+    LOG(INFO) << "Input buffer overflow";
+    ASSERT(false);
+    data_len_ = 0;
+  }
+
+  int len = socket_->Recv(buffer_ + data_len_, buffer_size_ - data_len_);
+  if (len < 0) {
+    // TODO: Do something better like forwarding the error to the user.
+    LOG_ERR(INFO) << "Recv";
+    return;
+  }
+
+  data_len_ += len;
+
+  ProcessInput(buffer_, &data_len_);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+// This is a SSL v2 CLIENT_HELLO message.
+// TODO: Should this have a session id? The response doesn't have a
+// certificate, so the hello should have a session id.
+static const uint8 kSslClientHello[] = {
+  0x80, 0x46,                                            // msg len
+  0x01,                                                  // CLIENT_HELLO
+  0x03, 0x01,                                            // SSL 3.1
+  0x00, 0x2d,                                            // ciphersuite len
+  0x00, 0x00,                                            // session id len
+  0x00, 0x10,                                            // challenge len
+  0x01, 0x00, 0x80, 0x03, 0x00, 0x80, 0x07, 0x00, 0xc0,  // ciphersuites
+  0x06, 0x00, 0x40, 0x02, 0x00, 0x80, 0x04, 0x00, 0x80,  //
+  0x00, 0x00, 0x04, 0x00, 0xfe, 0xff, 0x00, 0x00, 0x0a,  //
+  0x00, 0xfe, 0xfe, 0x00, 0x00, 0x09, 0x00, 0x00, 0x64,  //
+  0x00, 0x00, 0x62, 0x00, 0x00, 0x03, 0x00, 0x00, 0x06,  //
+  0x1f, 0x17, 0x0c, 0xa6, 0x2f, 0x00, 0x78, 0xfc,        // challenge
+  0x46, 0x55, 0x2e, 0xb1, 0x83, 0x39, 0xf1, 0xea         //
+};
+
+// This is a TLSv1 SERVER_HELLO message.
+static const uint8 kSslServerHello[] = {
+  0x16,                                            // handshake message
+  0x03, 0x01,                                      // SSL 3.1
+  0x00, 0x4a,                                      // message len
+  0x02,                                            // SERVER_HELLO
+  0x00, 0x00, 0x46,                                // handshake len
+  0x03, 0x01,                                      // SSL 3.1
+  0x42, 0x85, 0x45, 0xa7, 0x27, 0xa9, 0x5d, 0xa0,  // server random
+  0xb3, 0xc5, 0xe7, 0x53, 0xda, 0x48, 0x2b, 0x3f,  //
+  0xc6, 0x5a, 0xca, 0x89, 0xc1, 0x58, 0x52, 0xa1,  //
+  0x78, 0x3c, 0x5b, 0x17, 0x46, 0x00, 0x85, 0x3f,  //
+  0x20,                                            // session id len
+  0x0e, 0xd3, 0x06, 0x72, 0x5b, 0x5b, 0x1b, 0x5f,  // session id
+  0x15, 0xac, 0x13, 0xf9, 0x88, 0x53, 0x9d, 0x9b,  //
+  0xe8, 0x3d, 0x7b, 0x0c, 0x30, 0x32, 0x6e, 0x38,  //
+  0x4d, 0xa2, 0x75, 0x57, 0x41, 0x6c, 0x34, 0x5c,  //
+  0x00, 0x04,                                      // RSA/RC4-128/MD5
+  0x00                                             // null compression
+};
+
+AsyncSSLSocket::AsyncSSLSocket(AsyncSocket* socket)
+    : BufferedReadAdapter(socket, 1024) {
+}
+
+int AsyncSSLSocket::Connect(const SocketAddress& addr) {
+  // Begin buffering before we connect, so that there isn't a race condition
+  // between potential senders and receiving the OnConnectEvent signal
+  BufferInput(true);
+  return BufferedReadAdapter::Connect(addr);
+}
+
+void AsyncSSLSocket::OnConnectEvent(AsyncSocket * socket) {
+  ASSERT(socket == socket_);
+  // TODO: we could buffer output too...
+  VERIFY(sizeof(kSslClientHello) ==
+      DirectSend(kSslClientHello, sizeof(kSslClientHello)));
+}
+
+void AsyncSSLSocket::ProcessInput(char* data, size_t* len) {
+  if (*len < sizeof(kSslServerHello))
+    return;
+
+  if (memcmp(kSslServerHello, data, sizeof(kSslServerHello)) != 0) {
+    Close();
+    SignalCloseEvent(this, 0);  // TODO: error code?
+    return;
+  }
+
+  *len -= sizeof(kSslServerHello);
+  if (*len > 0) {
+    memmove(data, data + sizeof(kSslServerHello), *len);
+  }
+
+  bool remainder = (*len > 0);
+  BufferInput(false);
+  SignalConnectEvent(this);
+
+  // FIX: if SignalConnect causes the socket to be destroyed, we are in trouble
+  if (remainder)
+    SignalReadEvent(this);
+}
+
+AsyncSSLServerSocket::AsyncSSLServerSocket(AsyncSocket* socket)
+     : BufferedReadAdapter(socket, 1024) {
+  BufferInput(true);
+}
+
+void AsyncSSLServerSocket::ProcessInput(char* data, size_t* len) {
+  // We only accept client hello messages.
+  if (*len < sizeof(kSslClientHello)) {
+    return;
+  }
+
+  if (memcmp(kSslClientHello, data, sizeof(kSslClientHello)) != 0) {
+    Close();
+    SignalCloseEvent(this, 0);
+    return;
+  }
+
+  *len -= sizeof(kSslClientHello);
+
+  // Clients should not send more data until the handshake is completed.
+  ASSERT(*len == 0);
+
+  // Send a server hello back to the client.
+  DirectSend(kSslServerHello, sizeof(kSslServerHello));
+
+  // Handshake completed for us, redirect input to our parent.
+  BufferInput(false);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+AsyncHttpsProxySocket::AsyncHttpsProxySocket(AsyncSocket* socket,
+                                             const std::string& user_agent,
+                                             const SocketAddress& proxy,
+                                             const std::string& username,
+                                             const CryptString& password)
+  : BufferedReadAdapter(socket, 1024), proxy_(proxy), agent_(user_agent),
+    user_(username), pass_(password), force_connect_(false), state_(PS_ERROR),
+    context_(0) {
+}
+
+AsyncHttpsProxySocket::~AsyncHttpsProxySocket() {
+  delete context_;
+}
+
+int AsyncHttpsProxySocket::Connect(const SocketAddress& addr) {
+  int ret;
+  LOG(LS_VERBOSE) << "AsyncHttpsProxySocket::Connect("
+                  << proxy_.ToString() << ")";
+  dest_ = addr;
+  state_ = PS_INIT;
+  if (ShouldIssueConnect()) {
+    BufferInput(true);
+  }
+  ret = BufferedReadAdapter::Connect(proxy_);
+  // TODO: Set state_ appropriately if Connect fails.
+  return ret;
+}
+
+SocketAddress AsyncHttpsProxySocket::GetRemoteAddress() const {
+  return dest_;
+}
+
+int AsyncHttpsProxySocket::Close() {
+  headers_.clear();
+  state_ = PS_ERROR;
+  dest_.Clear();
+  delete context_;
+  context_ = NULL;
+  return BufferedReadAdapter::Close();
+}
+
+Socket::ConnState AsyncHttpsProxySocket::GetState() const {
+  if (state_ < PS_TUNNEL) {
+    return CS_CONNECTING;
+  } else if (state_ == PS_TUNNEL) {
+    return CS_CONNECTED;
+  } else {
+    return CS_CLOSED;
+  }
+}
+
+void AsyncHttpsProxySocket::OnConnectEvent(AsyncSocket * socket) {
+  LOG(LS_VERBOSE) << "AsyncHttpsProxySocket::OnConnectEvent";
+  if (!ShouldIssueConnect()) {
+    state_ = PS_TUNNEL;
+    BufferedReadAdapter::OnConnectEvent(socket);
+    return;
+  }
+  SendRequest();
+}
+
+void AsyncHttpsProxySocket::OnCloseEvent(AsyncSocket * socket, int err) {
+  LOG(LS_VERBOSE) << "AsyncHttpsProxySocket::OnCloseEvent(" << err << ")";
+  if ((state_ == PS_WAIT_CLOSE) && (err == 0)) {
+    state_ = PS_ERROR;
+    Connect(dest_);
+  } else {
+    BufferedReadAdapter::OnCloseEvent(socket, err);
+  }
+}
+
+void AsyncHttpsProxySocket::ProcessInput(char* data, size_t* len) {
+  size_t start = 0;
+  for (size_t pos = start; state_ < PS_TUNNEL && pos < *len;) {
+    if (state_ == PS_SKIP_BODY) {
+      size_t consume = _min(*len - pos, content_length_);
+      pos += consume;
+      start = pos;
+      content_length_ -= consume;
+      if (content_length_ == 0) {
+        EndResponse();
+      }
+      continue;
+    }
+
+    if (data[pos++] != '\n')
+      continue;
+
+    size_t len = pos - start - 1;
+    if ((len > 0) && (data[start + len - 1] == '\r'))
+      --len;
+
+    data[start + len] = 0;
+    ProcessLine(data + start, len);
+    start = pos;
+  }
+
+  *len -= start;
+  if (*len > 0) {
+    memmove(data, data + start, *len);
+  }
+
+  if (state_ != PS_TUNNEL)
+    return;
+
+  bool remainder = (*len > 0);
+  BufferInput(false);
+  SignalConnectEvent(this);
+
+  // FIX: if SignalConnect causes the socket to be destroyed, we are in trouble
+  if (remainder)
+    SignalReadEvent(this);  // TODO: signal this??
+}
+
+bool AsyncHttpsProxySocket::ShouldIssueConnect() const {
+  // TODO: Think about whether a more sophisticated test
+  // than dest port == 80 is needed.
+  return force_connect_ || (dest_.port() != 80);
+}
+
+void AsyncHttpsProxySocket::SendRequest() {
+  std::stringstream ss;
+  ss << "CONNECT " << dest_.ToString() << " HTTP/1.0\r\n";
+  ss << "User-Agent: " << agent_ << "\r\n";
+  ss << "Host: " << dest_.IPAsString() << "\r\n";
+  ss << "Content-Length: 0\r\n";
+  ss << "Proxy-Connection: Keep-Alive\r\n";
+  ss << headers_;
+  ss << "\r\n";
+  std::string str = ss.str();
+  DirectSend(str.c_str(), str.size());
+  state_ = PS_LEADER;
+  expect_close_ = true;
+  content_length_ = 0;
+  headers_.clear();
+
+  LOG(LS_VERBOSE) << "AsyncHttpsProxySocket >> " << str;
+}
+
+void AsyncHttpsProxySocket::ProcessLine(char * data, size_t len) {
+  LOG(LS_VERBOSE) << "AsyncHttpsProxySocket << " << data;
+
+  if (len == 0) {
+    if (state_ == PS_TUNNEL_HEADERS) {
+      state_ = PS_TUNNEL;
+    } else if (state_ == PS_ERROR_HEADERS) {
+      Error(defer_error_);
+      return;
+    } else if (state_ == PS_SKIP_HEADERS) {
+      if (content_length_) {
+        state_ = PS_SKIP_BODY;
+      } else {
+        EndResponse();
+        return;
+      }
+    } else {
+      static bool report = false;
+      if (!unknown_mechanisms_.empty() && !report) {
+        report = true;
+        std::string msg(
+          "Unable to connect to the Google Talk service due to an incompatibility "
+          "with your proxy.\r\nPlease help us resolve this issue by submitting the "
+          "following information to us using our technical issue submission form "
+          "at:\r\n\r\n"
+          "http://www.google.com/support/talk/bin/request.py\r\n\r\n"
+          "We apologize for the inconvenience.\r\n\r\n"
+          "Information to submit to Google: "
+          );
+        //std::string msg("Please report the following information to foo@bar.com:\r\nUnknown methods: ");
+        msg.append(unknown_mechanisms_);
+#ifdef WIN32
+        MessageBoxA(0, msg.c_str(), "Oops!", MB_OK);
+#endif
+#ifdef POSIX
+        // TODO: Raise a signal so the UI can be separated.
+        LOG(LS_ERROR) << "Oops!\n\n" << msg;
+#endif
+      }
+      // Unexpected end of headers
+      Error(0);
+      return;
+    }
+  } else if (state_ == PS_LEADER) {
+    unsigned int code;
+    if (sscanf(data, "HTTP/%*u.%*u %u", &code) != 1) {
+      Error(0);
+      return;
+    }
+    switch (code) {
+    case 200:
+      // connection good!
+      state_ = PS_TUNNEL_HEADERS;
+      return;
+#if defined(HTTP_STATUS_PROXY_AUTH_REQ) && (HTTP_STATUS_PROXY_AUTH_REQ != 407)
+#error Wrong code for HTTP_STATUS_PROXY_AUTH_REQ
+#endif
+    case 407:  // HTTP_STATUS_PROXY_AUTH_REQ
+      state_ = PS_AUTHENTICATE;
+      return;
+    default:
+      defer_error_ = 0;
+      state_ = PS_ERROR_HEADERS;
+      return;
+    }
+  } else if ((state_ == PS_AUTHENTICATE)
+             && (_strnicmp(data, "Proxy-Authenticate:", 19) == 0)) {
+    std::string response, auth_method;
+    switch (HttpAuthenticate(data + 19, len - 19,
+                             proxy_, "CONNECT", "/",
+                             user_, pass_, context_, response, auth_method)) {
+    case HAR_IGNORE:
+      LOG(LS_VERBOSE) << "Ignoring Proxy-Authenticate: " << auth_method;
+      if (!unknown_mechanisms_.empty())
+        unknown_mechanisms_.append(", ");
+      unknown_mechanisms_.append(auth_method);
+      break;
+    case HAR_RESPONSE:
+      headers_ = "Proxy-Authorization: ";
+      headers_.append(response);
+      headers_.append("\r\n");
+      state_ = PS_SKIP_HEADERS;
+      unknown_mechanisms_.clear();
+      break;
+    case HAR_CREDENTIALS:
+      defer_error_ = SOCKET_EACCES;
+      state_ = PS_ERROR_HEADERS;
+      unknown_mechanisms_.clear();
+      break;
+    case HAR_ERROR:
+      defer_error_ = 0;
+      state_ = PS_ERROR_HEADERS;
+      unknown_mechanisms_.clear();
+      break;
+    }
+  } else if (_strnicmp(data, "Content-Length:", 15) == 0) {
+    content_length_ = strtoul(data + 15, 0, 0);
+  } else if (_strnicmp(data, "Proxy-Connection: Keep-Alive", 28) == 0) {
+    expect_close_ = false;
+    /*
+  } else if (_strnicmp(data, "Connection: close", 17) == 0) {
+    expect_close_ = true;
+    */
+  }
+}
+
+void AsyncHttpsProxySocket::EndResponse() {
+  if (!expect_close_) {
+    SendRequest();
+    return;
+  }
+
+  // No point in waiting for the server to close... let's close now
+  // TODO: Refactor out PS_WAIT_CLOSE
+  state_ = PS_WAIT_CLOSE;
+  BufferedReadAdapter::Close();
+  OnCloseEvent(this, 0);
+}
+
+void AsyncHttpsProxySocket::Error(int error) {
+  BufferInput(false);
+  Close();
+  SetError(error);
+  SignalCloseEvent(this, error);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+AsyncSocksProxySocket::AsyncSocksProxySocket(AsyncSocket* socket,
+                                             const SocketAddress& proxy,
+                                             const std::string& username,
+                                             const CryptString& password)
+    : BufferedReadAdapter(socket, 1024), state_(SS_ERROR), proxy_(proxy),
+      user_(username), pass_(password) {
+}
+
+int AsyncSocksProxySocket::Connect(const SocketAddress& addr) {
+  int ret;
+  dest_ = addr;
+  state_ = SS_INIT;
+  BufferInput(true);
+  ret = BufferedReadAdapter::Connect(proxy_);
+  // TODO: Set state_ appropriately if Connect fails.
+  return ret;
+}
+
+SocketAddress AsyncSocksProxySocket::GetRemoteAddress() const {
+  return dest_;
+}
+
+int AsyncSocksProxySocket::Close() {
+  state_ = SS_ERROR;
+  dest_.Clear();
+  return BufferedReadAdapter::Close();
+}
+
+Socket::ConnState AsyncSocksProxySocket::GetState() const {
+  if (state_ < SS_TUNNEL) {
+    return CS_CONNECTING;
+  } else if (state_ == SS_TUNNEL) {
+    return CS_CONNECTED;
+  } else {
+    return CS_CLOSED;
+  }
+}
+
+void AsyncSocksProxySocket::OnConnectEvent(AsyncSocket* socket) {
+  SendHello();
+}
+
+void AsyncSocksProxySocket::ProcessInput(char* data, size_t* len) {
+  ASSERT(state_ < SS_TUNNEL);
+
+  ByteBuffer response(data, *len);
+
+  if (state_ == SS_HELLO) {
+    uint8 ver, method;
+    if (!response.ReadUInt8(&ver) ||
+        !response.ReadUInt8(&method))
+      return;
+
+    if (ver != 5) {
+      Error(0);
+      return;
+    }
+
+    if (method == 0) {
+      SendConnect();
+    } else if (method == 2) {
+      SendAuth();
+    } else {
+      Error(0);
+      return;
+    }
+  } else if (state_ == SS_AUTH) {
+    uint8 ver, status;
+    if (!response.ReadUInt8(&ver) ||
+        !response.ReadUInt8(&status))
+      return;
+
+    if ((ver != 1) || (status != 0)) {
+      Error(SOCKET_EACCES);
+      return;
+    }
+
+    SendConnect();
+  } else if (state_ == SS_CONNECT) {
+    uint8 ver, rep, rsv, atyp;
+    if (!response.ReadUInt8(&ver) ||
+        !response.ReadUInt8(&rep) ||
+        !response.ReadUInt8(&rsv) ||
+        !response.ReadUInt8(&atyp))
+      return;
+
+    if ((ver != 5) || (rep != 0)) {
+      Error(0);
+      return;
+    }
+
+    uint16 port;
+    if (atyp == 1) {
+      uint32 addr;
+      if (!response.ReadUInt32(&addr) ||
+          !response.ReadUInt16(&port))
+        return;
+      LOG(LS_VERBOSE) << "Bound on " << addr << ":" << port;
+    } else if (atyp == 3) {
+      uint8 len;
+      std::string addr;
+      if (!response.ReadUInt8(&len) ||
+          !response.ReadString(&addr, len) ||
+          !response.ReadUInt16(&port))
+        return;
+      LOG(LS_VERBOSE) << "Bound on " << addr << ":" << port;
+    } else if (atyp == 4) {
+      std::string addr;
+      if (!response.ReadString(&addr, 16) ||
+          !response.ReadUInt16(&port))
+        return;
+      LOG(LS_VERBOSE) << "Bound on <IPV6>:" << port;
+    } else {
+      Error(0);
+      return;
+    }
+
+    state_ = SS_TUNNEL;
+  }
+
+  // Consume parsed data
+  *len = response.Length();
+  memcpy(data, response.Data(), *len);
+
+  if (state_ != SS_TUNNEL)
+    return;
+
+  bool remainder = (*len > 0);
+  BufferInput(false);
+  SignalConnectEvent(this);
+
+  // FIX: if SignalConnect causes the socket to be destroyed, we are in trouble
+  if (remainder)
+    SignalReadEvent(this);  // TODO: signal this??
+}
+
+void AsyncSocksProxySocket::SendHello() {
+  ByteBuffer request;
+  request.WriteUInt8(5);    // Socks Version
+  if (user_.empty()) {
+    request.WriteUInt8(1);  // Authentication Mechanisms
+    request.WriteUInt8(0);  // No authentication
+  } else {
+    request.WriteUInt8(2);  // Authentication Mechanisms
+    request.WriteUInt8(0);  // No authentication
+    request.WriteUInt8(2);  // Username/Password
+  }
+  DirectSend(request.Data(), request.Length());
+  state_ = SS_HELLO;
+}
+
+void AsyncSocksProxySocket::SendAuth() {
+  ByteBuffer request;
+  request.WriteUInt8(1);           // Negotiation Version
+  request.WriteUInt8(static_cast<uint8>(user_.size()));
+  request.WriteString(user_);      // Username
+  request.WriteUInt8(static_cast<uint8>(pass_.GetLength()));
+  size_t len = pass_.GetLength() + 1;
+  char * sensitive = new char[len];
+  pass_.CopyTo(sensitive, true);
+  request.WriteString(sensitive);  // Password
+  memset(sensitive, 0, len);
+  delete [] sensitive;
+  DirectSend(request.Data(), request.Length());
+  state_ = SS_AUTH;
+}
+
+void AsyncSocksProxySocket::SendConnect() {
+  ByteBuffer request;
+  request.WriteUInt8(5);              // Socks Version
+  request.WriteUInt8(1);              // CONNECT
+  request.WriteUInt8(0);              // Reserved
+  if (dest_.IsUnresolved()) {
+    std::string hostname = dest_.IPAsString();
+    request.WriteUInt8(3);            // DOMAINNAME
+    request.WriteUInt8(static_cast<uint8>(hostname.size()));
+    request.WriteString(hostname);    // Destination Hostname
+  } else {
+    request.WriteUInt8(1);            // IPV4
+    request.WriteUInt32(dest_.ip());  // Destination IP
+  }
+  request.WriteUInt16(dest_.port());  // Destination Port
+  DirectSend(request.Data(), request.Length());
+  state_ = SS_CONNECT;
+}
+
+void AsyncSocksProxySocket::Error(int error) {
+  state_ = SS_ERROR;
+  BufferInput(false);
+  Close();
+  SetError(SOCKET_EACCES);
+  SignalCloseEvent(this, error);
+}
+
+AsyncSocksProxyServerSocket::AsyncSocksProxyServerSocket(AsyncSocket* socket)
+    : AsyncProxyServerSocket(socket, kBufferSize), state_(SS_HELLO) {
+  BufferInput(true);
+}
+
+void AsyncSocksProxyServerSocket::ProcessInput(char* data, size_t* len) {
+  // TODO: See if the whole message has arrived
+  ASSERT(state_ < SS_CONNECT_PENDING);
+
+  ByteBuffer response(data, *len);
+  if (state_ == SS_HELLO) {
+    HandleHello(&response);
+  } else if (state_ == SS_AUTH) {
+    HandleAuth(&response);
+  } else if (state_ == SS_CONNECT) {
+    HandleConnect(&response);
+  }
+
+  // Consume parsed data
+  *len = response.Length();
+  memcpy(data, response.Data(), *len);
+}
+
+void AsyncSocksProxyServerSocket::DirectSend(const ByteBuffer& buf) {
+  BufferedReadAdapter::DirectSend(buf.Data(), buf.Length());
+}
+
+void AsyncSocksProxyServerSocket::HandleHello(ByteBuffer* request) {
+  uint8 ver, num_methods;
+  if (!request->ReadUInt8(&ver) ||
+      !request->ReadUInt8(&num_methods)) {
+    Error(0);
+    return;
+  }
+
+  if (ver != 5) {
+    Error(0);
+    return;
+  }
+
+  // Handle either no-auth (0) or user/pass auth (2)
+  uint8 method = 0xFF;
+  if (num_methods > 0 && !request->ReadUInt8(&method)) {
+    Error(0);
+    return;
+  }
+
+  // TODO: Ask the server which method to use.
+  SendHelloReply(method);
+  if (method == 0) {
+    state_ = SS_CONNECT;
+  } else if (method == 2) {
+    state_ = SS_AUTH;
+  } else {
+    state_ = SS_ERROR;
+  }
+}
+
+void AsyncSocksProxyServerSocket::SendHelloReply(int method) {
+  ByteBuffer response;
+  response.WriteUInt8(5);  // Socks Version
+  response.WriteUInt8(method);  // Auth method
+  DirectSend(response);
+}
+
+void AsyncSocksProxyServerSocket::HandleAuth(ByteBuffer* request) {
+  uint8 ver, user_len, pass_len;
+  std::string user, pass;
+  if (!request->ReadUInt8(&ver) ||
+      !request->ReadUInt8(&user_len) ||
+      !request->ReadString(&user, user_len) ||
+      !request->ReadUInt8(&pass_len) ||
+      !request->ReadString(&pass, pass_len)) {
+    Error(0);
+    return;
+  }
+
+  // TODO: Allow for checking of credentials.
+  SendAuthReply(0);
+  state_ = SS_CONNECT;
+}
+
+void AsyncSocksProxyServerSocket::SendAuthReply(int result) {
+  ByteBuffer response;
+  response.WriteUInt8(1);  // Negotiation Version
+  response.WriteUInt8(result);
+  DirectSend(response);
+}
+
+void AsyncSocksProxyServerSocket::HandleConnect(ByteBuffer* request) {
+  uint8 ver, command, reserved, addr_type;
+  uint32 ip;
+  uint16 port;
+  if (!request->ReadUInt8(&ver) ||
+      !request->ReadUInt8(&command) ||
+      !request->ReadUInt8(&reserved) ||
+      !request->ReadUInt8(&addr_type) ||
+      !request->ReadUInt32(&ip) ||
+      !request->ReadUInt16(&port)) {
+      Error(0);
+      return;
+  }
+
+  if (ver != 5 || command != 1 ||
+      reserved != 0 || addr_type != 1) {
+      Error(0);
+      return;
+  }
+
+  SignalConnectRequest(this, SocketAddress(ip, port));
+  state_ = SS_CONNECT_PENDING;
+}
+
+void AsyncSocksProxyServerSocket::SendConnectResult(int result,
+                                                    const SocketAddress& addr) {
+  if (state_ != SS_CONNECT_PENDING)
+    return;
+
+  ByteBuffer response;
+  response.WriteUInt8(5);  // Socks version
+  response.WriteUInt8((result != 0));  // 0x01 is generic error
+  response.WriteUInt8(0);  // reserved
+  response.WriteUInt8(1);  // IPv4 address
+  response.WriteUInt32(addr.ip());
+  response.WriteUInt16(addr.port());
+  DirectSend(response);
+  BufferInput(false);
+  state_ = SS_TUNNEL;
+}
+
+void AsyncSocksProxyServerSocket::Error(int error) {
+  state_ = SS_ERROR;
+  BufferInput(false);
+  Close();
+  SetError(SOCKET_EACCES);
+  SignalCloseEvent(this, error);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+LoggingSocketAdapter::LoggingSocketAdapter(AsyncSocket* socket,
+                                           LoggingSeverity level,
+                                           const char * label, bool hex_mode)
+    : AsyncSocketAdapter(socket), level_(level), hex_mode_(hex_mode) {
+  label_.append("[");
+  label_.append(label);
+  label_.append("]");
+}
+
+int LoggingSocketAdapter::Send(const void *pv, size_t cb) {
+  int res = AsyncSocketAdapter::Send(pv, cb);
+  if (res > 0)
+    LogMultiline(level_, label_.c_str(), false, pv, res, hex_mode_, &lms_);
+  return res;
+}
+
+int LoggingSocketAdapter::SendTo(const void *pv, size_t cb,
+                             const SocketAddress& addr) {
+  int res = AsyncSocketAdapter::SendTo(pv, cb, addr);
+  if (res > 0)
+    LogMultiline(level_, label_.c_str(), false, pv, res, hex_mode_, &lms_);
+  return res;
+}
+
+int LoggingSocketAdapter::Recv(void *pv, size_t cb) {
+  int res = AsyncSocketAdapter::Recv(pv, cb);
+  if (res > 0)
+    LogMultiline(level_, label_.c_str(), true, pv, res, hex_mode_, &lms_);
+  return res;
+}
+
+int LoggingSocketAdapter::RecvFrom(void *pv, size_t cb, SocketAddress *paddr) {
+  int res = AsyncSocketAdapter::RecvFrom(pv, cb, paddr);
+  if (res > 0)
+    LogMultiline(level_, label_.c_str(), true, pv, res, hex_mode_, &lms_);
+  return res;
+}
+
+int LoggingSocketAdapter::Close() {
+  LogMultiline(level_, label_.c_str(), false, NULL, 0, hex_mode_, &lms_);
+  LogMultiline(level_, label_.c_str(), true, NULL, 0, hex_mode_, &lms_);
+  LOG_V(level_) << label_ << " Closed locally";
+  return socket_->Close();
+}
+
+void LoggingSocketAdapter::OnConnectEvent(AsyncSocket * socket) {
+  LOG_V(level_) << label_ << " Connected";
+  AsyncSocketAdapter::OnConnectEvent(socket);
+}
+
+void LoggingSocketAdapter::OnCloseEvent(AsyncSocket * socket, int err) {
+  LogMultiline(level_, label_.c_str(), false, NULL, 0, hex_mode_, &lms_);
+  LogMultiline(level_, label_.c_str(), true, NULL, 0, hex_mode_, &lms_);
+  LOG_V(level_) << label_ << " Closed with error: " << err;
+  AsyncSocketAdapter::OnCloseEvent(socket, err);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
diff --git a/talk/base/socketadapters.h b/talk/base/socketadapters.h
new file mode 100644
index 0000000..320da6f
--- /dev/null
+++ b/talk/base/socketadapters.h
@@ -0,0 +1,261 @@
+/*
+ * 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_BASE_SOCKETADAPTERS_H_
+#define TALK_BASE_SOCKETADAPTERS_H_
+
+#include <map>
+#include <string>
+
+#include "talk/base/asyncsocket.h"
+#include "talk/base/cryptstring.h"
+#include "talk/base/logging.h"
+
+namespace talk_base {
+
+struct HttpAuthContext;
+class ByteBuffer;
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Implements a socket adapter that can buffer and process data internally,
+// as in the case of connecting to a proxy, where you must speak the proxy
+// protocol before commencing normal socket behavior.
+class BufferedReadAdapter : public AsyncSocketAdapter {
+ public:
+  BufferedReadAdapter(AsyncSocket* socket, size_t buffer_size);
+  virtual ~BufferedReadAdapter();
+
+  virtual int Send(const void* pv, size_t cb);
+  virtual int Recv(void* pv, size_t cb);
+
+ protected:
+  int DirectSend(const void* pv, size_t cb) {
+    return AsyncSocketAdapter::Send(pv, cb);
+  }
+
+  void BufferInput(bool on = true);
+  virtual void ProcessInput(char* data, size_t* len) = 0;
+
+  virtual void OnReadEvent(AsyncSocket * socket);
+
+ private:
+  char * buffer_;
+  size_t buffer_size_, data_len_;
+  bool buffering_;
+  DISALLOW_EVIL_CONSTRUCTORS(BufferedReadAdapter);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Interface for implementing proxy server sockets.
+class AsyncProxyServerSocket : public BufferedReadAdapter {
+ public:
+  AsyncProxyServerSocket(AsyncSocket* socket, size_t buffer_size)
+      : BufferedReadAdapter(socket, buffer_size) {}
+  sigslot::signal2<AsyncProxyServerSocket*,
+                   const SocketAddress&>  SignalConnectRequest;
+  virtual void SendConnectResult(int err, const SocketAddress& addr) = 0;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Implements a socket adapter that performs the client side of a
+// fake SSL handshake. Used for "ssltcp" P2P functionality.
+class AsyncSSLSocket : public BufferedReadAdapter {
+ public:
+  explicit AsyncSSLSocket(AsyncSocket* socket);
+
+  virtual int Connect(const SocketAddress& addr);
+
+ protected:
+  virtual void OnConnectEvent(AsyncSocket* socket);
+  virtual void ProcessInput(char* data, size_t* len);
+  DISALLOW_EVIL_CONSTRUCTORS(AsyncSSLSocket);
+};
+
+// Implements a socket adapter that performs the server side of a
+// fake SSL handshake. Used when implementing a relay server that does "ssltcp".
+class AsyncSSLServerSocket : public BufferedReadAdapter {
+ public:
+  explicit AsyncSSLServerSocket(AsyncSocket* socket);
+
+ protected:
+  virtual void ProcessInput(char* data, size_t* len);
+  DISALLOW_EVIL_CONSTRUCTORS(AsyncSSLServerSocket);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Implements a socket adapter that speaks the HTTP/S proxy protocol.
+class AsyncHttpsProxySocket : public BufferedReadAdapter {
+ public:
+  AsyncHttpsProxySocket(AsyncSocket* socket, const std::string& user_agent,
+    const SocketAddress& proxy,
+    const std::string& username, const CryptString& password);
+  virtual ~AsyncHttpsProxySocket();
+
+  // If connect is forced, the adapter will always issue an HTTP CONNECT to the
+  // target address.  Otherwise, it will connect only if the destination port
+  // is not port 80.
+  void SetForceConnect(bool force) { force_connect_ = force; }
+
+  virtual int Connect(const SocketAddress& addr);
+  virtual SocketAddress GetRemoteAddress() const;
+  virtual int Close();
+  virtual ConnState GetState() const;
+
+ protected:
+  virtual void OnConnectEvent(AsyncSocket* socket);
+  virtual void OnCloseEvent(AsyncSocket* socket, int err);
+  virtual void ProcessInput(char* data, size_t* len);
+
+  bool ShouldIssueConnect() const;
+  void SendRequest();
+  void ProcessLine(char* data, size_t len);
+  void EndResponse();
+  void Error(int error);
+
+ private:
+  SocketAddress proxy_, dest_;
+  std::string agent_, user_, headers_;
+  CryptString pass_;
+  bool force_connect_;
+  size_t content_length_;
+  int defer_error_;
+  bool expect_close_;
+  enum ProxyState {
+    PS_INIT, PS_LEADER, PS_AUTHENTICATE, PS_SKIP_HEADERS, PS_ERROR_HEADERS,
+    PS_TUNNEL_HEADERS, PS_SKIP_BODY, PS_TUNNEL, PS_WAIT_CLOSE, PS_ERROR
+  } state_;
+  HttpAuthContext * context_;
+  std::string unknown_mechanisms_;
+  DISALLOW_EVIL_CONSTRUCTORS(AsyncHttpsProxySocket);
+};
+
+/* TODO: Implement this.
+class AsyncHttpsProxyServerSocket : public AsyncProxyServerSocket {
+ public:
+  explicit AsyncHttpsProxyServerSocket(AsyncSocket* socket);
+
+ private:
+  virtual void ProcessInput(char * data, size_t& len);
+  void Error(int error);
+  DISALLOW_EVIL_CONSTRUCTORS(AsyncHttpsProxyServerSocket);
+};
+*/
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Implements a socket adapter that speaks the SOCKS proxy protocol.
+class AsyncSocksProxySocket : public BufferedReadAdapter {
+ public:
+  AsyncSocksProxySocket(AsyncSocket* socket, const SocketAddress& proxy,
+    const std::string& username, const CryptString& password);
+
+  virtual int Connect(const SocketAddress& addr);
+  virtual SocketAddress GetRemoteAddress() const;
+  virtual int Close();
+  virtual ConnState GetState() const;
+
+ protected:
+  virtual void OnConnectEvent(AsyncSocket* socket);
+  virtual void ProcessInput(char* data, size_t* len);
+
+  void SendHello();
+  void SendConnect();
+  void SendAuth();
+  void Error(int error);
+
+ private:
+  enum State {
+    SS_INIT, SS_HELLO, SS_AUTH, SS_CONNECT, SS_TUNNEL, SS_ERROR
+  };
+  State state_;
+  SocketAddress proxy_, dest_;
+  std::string user_;
+  CryptString pass_;
+  DISALLOW_EVIL_CONSTRUCTORS(AsyncSocksProxySocket);
+};
+
+// Implements a proxy server socket for the SOCKS protocol.
+class AsyncSocksProxyServerSocket : public AsyncProxyServerSocket {
+ public:
+  explicit AsyncSocksProxyServerSocket(AsyncSocket* socket);
+
+ private:
+  virtual void ProcessInput(char* data, size_t* len);
+  void DirectSend(const ByteBuffer& buf);
+
+  void HandleHello(ByteBuffer* request);
+  void SendHelloReply(int method);
+  void HandleAuth(ByteBuffer* request);
+  void SendAuthReply(int result);
+  void HandleConnect(ByteBuffer* request);
+  virtual void SendConnectResult(int result, const SocketAddress& addr);
+
+  void Error(int error);
+
+  static const int kBufferSize = 1024;
+  enum State {
+    SS_HELLO, SS_AUTH, SS_CONNECT, SS_CONNECT_PENDING, SS_TUNNEL, SS_ERROR
+  };
+  State state_;
+  DISALLOW_EVIL_CONSTRUCTORS(AsyncSocksProxyServerSocket);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Implements a socket adapter that logs everything that it sends and receives.
+class LoggingSocketAdapter : public AsyncSocketAdapter {
+ public:
+  LoggingSocketAdapter(AsyncSocket* socket, LoggingSeverity level,
+                 const char * label, bool hex_mode = false);
+
+  virtual int Send(const void *pv, size_t cb);
+  virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr);
+  virtual int Recv(void *pv, size_t cb);
+  virtual int RecvFrom(void *pv, size_t cb, SocketAddress *paddr);
+  virtual int Close();
+
+ protected:
+  virtual void OnConnectEvent(AsyncSocket * socket);
+  virtual void OnCloseEvent(AsyncSocket * socket, int err);
+
+ private:
+  LoggingSeverity level_;
+  std::string label_;
+  bool hex_mode_;
+  LogMultilineState lms_;
+  DISALLOW_EVIL_CONSTRUCTORS(LoggingSocketAdapter);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_SOCKETADAPTERS_H_
diff --git a/talk/base/socketaddress.cc b/talk/base/socketaddress.cc
new file mode 100644
index 0000000..cbb0805
--- /dev/null
+++ b/talk/base/socketaddress.cc
@@ -0,0 +1,358 @@
+/*
+ * 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.
+ */
+
+#ifdef POSIX
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <unistd.h>
+#endif
+
+#include <sstream>
+
+#include "talk/base/byteorder.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/nethelpers.h"
+#include "talk/base/socketaddress.h"
+
+#ifdef WIN32
+// Win32 doesn't provide inet_aton, so we add our own version here.
+// Since inet_addr returns 0xFFFFFFFF on error, if we get this value
+// we need to test the input to see if the address really was 255.255.255.255.
+// This is slightly fragile, but better than doing nothing.
+int inet_aton(const char* cp, struct in_addr* inp) {
+  inp->s_addr = inet_addr(cp);
+  return (inp->s_addr == INADDR_NONE &&
+          strcmp(cp, "255.255.255.255") != 0) ? 0 : 1;
+}
+#endif  // WIN32
+
+namespace talk_base {
+
+SocketAddress::SocketAddress() {
+  Clear();
+}
+
+SocketAddress::SocketAddress(const std::string& hostname, int port) {
+  SetIP(hostname);
+  SetPort(port);
+}
+
+SocketAddress::SocketAddress(uint32 ip, int port) {
+  SetIP(ip);
+  SetPort(port);
+}
+
+SocketAddress::SocketAddress(const SocketAddress& addr) {
+  this->operator=(addr);
+}
+
+void SocketAddress::Clear() {
+  hostname_.clear();
+  ip_ = 0;
+  port_ = 0;
+}
+
+bool SocketAddress::IsNil() const {
+  return hostname_.empty() && (0 == ip_) && (0 == port_);
+}
+
+bool SocketAddress::IsComplete() const {
+  return (0 != ip_) && (0 != port_);
+}
+
+SocketAddress& SocketAddress::operator=(const SocketAddress& addr) {
+  hostname_ = addr.hostname_;
+  ip_ = addr.ip_;
+  port_ = addr.port_;
+  return *this;
+}
+
+void SocketAddress::SetIP(uint32 ip) {
+  hostname_.clear();
+  ip_ = ip;
+}
+
+void SocketAddress::SetIP(const std::string& hostname) {
+  hostname_ = hostname;
+  ip_ = StringToIP(hostname);
+}
+
+void SocketAddress::SetResolvedIP(uint32 ip) {
+  ip_ = ip;
+}
+
+void SocketAddress::SetPort(int port) {
+  ASSERT((0 <= port) && (port < 65536));
+  port_ = port;
+}
+
+uint32 SocketAddress::ip() const {
+  return ip_;
+}
+
+uint16 SocketAddress::port() const {
+  return port_;
+}
+
+std::string SocketAddress::IPAsString() const {
+  if (!hostname_.empty())
+    return hostname_;
+  return IPToString(ip_);
+}
+
+std::string SocketAddress::PortAsString() const {
+  std::ostringstream ost;
+  ost << port_;
+  return ost.str();
+}
+
+std::string SocketAddress::ToString() const {
+  std::ostringstream ost;
+  ost << IPAsString();
+  ost << ":";
+  ost << port();
+  return ost.str();
+}
+
+bool SocketAddress::FromString(const std::string& str) {
+  std::string::size_type pos = str.find(':');
+  if (std::string::npos == pos)
+    return false;
+  SetPort(strtoul(str.substr(pos + 1).c_str(), NULL, 10));
+  SetIP(str.substr(0, pos));
+  return true;
+}
+
+std::ostream& operator<<(std::ostream& os, const SocketAddress& addr) {
+  os << addr.IPAsString() << ":" << addr.port();
+  return os;
+}
+
+bool SocketAddress::IsAnyIP() const {
+  return (ip_ == 0);
+}
+
+bool SocketAddress::IsLoopbackIP() const {
+  if (0 == ip_) {
+    return (0 == stricmp(hostname_.c_str(), "localhost"));
+  } else {
+    return ((ip_ >> 24) == 127);
+  }
+}
+
+bool SocketAddress::IsLocalIP() const {
+  if (IsLoopbackIP())
+    return true;
+
+  std::vector<uint32> ips;
+  if (0 == ip_) {
+    if (!hostname_.empty()
+        && (0 == stricmp(hostname_.c_str(), GetHostname().c_str()))) {
+      return true;
+    }
+  } else if (GetLocalIPs(ips)) {
+    for (size_t i = 0; i < ips.size(); ++i) {
+      if (ips[i] == ip_) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+bool SocketAddress::IsPrivateIP() const {
+  return ((ip_ >> 24) == 127) ||
+         ((ip_ >> 24) == 10) ||
+         ((ip_ >> 20) == ((172 << 4) | 1)) ||
+         ((ip_ >> 16) == ((192 << 8) | 168)) ||
+         ((ip_ >> 16) == ((169 << 8) | 254));
+}
+
+bool SocketAddress::IsUnresolvedIP() const {
+  return IsAny() && !hostname_.empty();
+}
+
+bool SocketAddress::ResolveIP(bool force, int* error) {
+  if (hostname_.empty()) {
+    // nothing to resolve
+  } else if (!force && !IsAny()) {
+    // already resolved
+  } else {
+    LOG_F(LS_VERBOSE) << "(" << hostname_ << ")";
+    int errcode = 0;
+    if (hostent* pHost = SafeGetHostByName(hostname_.c_str(), &errcode)) {
+      ip_ = NetworkToHost32(*reinterpret_cast<uint32*>(pHost->h_addr_list[0]));
+      LOG_F(LS_VERBOSE) << "(" << hostname_ << ") resolved to: "
+                        << IPToString(ip_);
+      FreeHostEnt(pHost);
+    } else {
+      LOG_F(LS_ERROR) << "(" << hostname_ << ") err: " << errcode;
+    }
+    if (error) {
+      *error = errcode;
+    }
+  }
+  return (ip_ != 0);
+}
+
+bool SocketAddress::operator==(const SocketAddress& addr) const {
+  return EqualIPs(addr) && EqualPorts(addr);
+}
+
+bool SocketAddress::operator<(const SocketAddress& addr) const {
+  if (ip_ < addr.ip_)
+    return true;
+  else if (addr.ip_ < ip_)
+    return false;
+
+  // We only check hostnames if both IPs are zero.  This matches EqualIPs()
+  if (addr.ip_ == 0) {
+    if (hostname_ < addr.hostname_)
+      return true;
+    else if (addr.hostname_ < hostname_)
+      return false;
+  }
+
+  return port_ < addr.port_;
+}
+
+bool SocketAddress::EqualIPs(const SocketAddress& addr) const {
+  return (ip_ == addr.ip_) && ((ip_ != 0) || (hostname_ == addr.hostname_));
+}
+
+bool SocketAddress::EqualPorts(const SocketAddress& addr) const {
+  return (port_ == addr.port_);
+}
+
+size_t SocketAddress::Hash() const {
+  size_t h = 0;
+  h ^= ip_;
+  h ^= port_ | (port_ << 16);
+  return h;
+}
+
+size_t SocketAddress::Size_() const {
+  return sizeof(ip_) + sizeof(port_) + 2;
+}
+
+bool SocketAddress::Write_(char* buf, int len) const {
+  if (len < static_cast<int>(Size_()))
+    return false;
+  buf[0] = 0;
+  buf[1] = AF_INET;
+  SetBE16(buf + 2, port_);
+  SetBE32(buf + 4, ip_);
+  return true;
+}
+
+bool SocketAddress::Read_(const char* buf, int len) {
+  if (len < static_cast<int>(Size_()) || buf[1] != AF_INET)
+    return false;
+  port_ = GetBE16(buf + 2);
+  ip_ = GetBE32(buf + 4);
+  return true;
+}
+
+void SocketAddress::ToSockAddr(sockaddr_in* saddr) const {
+  memset(saddr, 0, sizeof(*saddr));
+  saddr->sin_family = AF_INET;
+  saddr->sin_port = HostToNetwork16(port_);
+  if (0 == ip_) {
+    saddr->sin_addr.s_addr = INADDR_ANY;
+  } else {
+    saddr->sin_addr.s_addr = HostToNetwork32(ip_);
+  }
+}
+
+bool SocketAddress::FromSockAddr(const sockaddr_in& saddr) {
+  if (saddr.sin_family != AF_INET)
+    return false;
+  SetIP(NetworkToHost32(saddr.sin_addr.s_addr));
+  SetPort(NetworkToHost16(saddr.sin_port));
+  return true;
+}
+
+std::string SocketAddress::IPToString(uint32 ip) {
+  std::ostringstream ost;
+  ost << ((ip >> 24) & 0xff);
+  ost << '.';
+  ost << ((ip >> 16) & 0xff);
+  ost << '.';
+  ost << ((ip >> 8) & 0xff);
+  ost << '.';
+  ost << ((ip >> 0) & 0xff);
+  return ost.str();
+}
+
+bool SocketAddress::StringToIP(const std::string& hostname, uint32* ip) {
+  in_addr addr;
+  if (inet_aton(hostname.c_str(), &addr) == 0)
+    return false;
+  *ip = NetworkToHost32(addr.s_addr);
+  return true;
+}
+
+uint32 SocketAddress::StringToIP(const std::string& hostname) {
+  uint32 ip = 0;
+  StringToIP(hostname, &ip);
+  return ip;
+}
+
+std::string SocketAddress::GetHostname() {
+  char hostname[256];
+  if (gethostname(hostname, ARRAY_SIZE(hostname)) == 0)
+    return hostname;
+  return "";
+}
+
+bool SocketAddress::GetLocalIPs(std::vector<uint32>& ips) {
+  ips.clear();
+
+  const std::string hostname = GetHostname();
+  if (hostname.empty())
+    return false;
+
+  int errcode;
+  if (hostent* pHost = SafeGetHostByName(hostname.c_str(), &errcode)) {
+    for (size_t i = 0; pHost->h_addr_list[i]; ++i) {
+      uint32 ip =
+        NetworkToHost32(*reinterpret_cast<uint32 *>(pHost->h_addr_list[i]));
+      ips.push_back(ip);
+    }
+    FreeHostEnt(pHost);
+    return !ips.empty();
+  }
+  LOG(LS_ERROR) << "gethostbyname err: " << errcode;
+  return false;
+}
+
+}  // namespace talk_base
diff --git a/talk/base/socketaddress.h b/talk/base/socketaddress.h
new file mode 100644
index 0000000..4af80c9
--- /dev/null
+++ b/talk/base/socketaddress.h
@@ -0,0 +1,191 @@
+/*
+ * 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_BASE_SOCKETADDRESS_H_
+#define TALK_BASE_SOCKETADDRESS_H_
+
+#include <string>
+#include <vector>
+#include <iosfwd>
+#include "talk/base/basictypes.h"
+#undef SetPort
+
+struct sockaddr_in;
+
+namespace talk_base {
+
+// Records an IP address and port, which are 32 and 16 bit integers,
+// respectively, both in <b>host byte-order</b>.
+class SocketAddress {
+ public:
+  // Creates a nil address.
+  SocketAddress();
+
+  // Creates the address with the given host and port.  If use_dns is true,
+  // the hostname will be immediately resolved to an IP (which may block for
+  // several seconds if DNS is not available).  Alternately, set use_dns to
+  // false, and then call Resolve() to complete resolution later, or use
+  // SetResolvedIP to set the IP explictly.
+  SocketAddress(const std::string& hostname, int port);
+
+  // Creates the address with the given IP and port.
+  SocketAddress(uint32 ip, int port);
+
+  // Creates a copy of the given address.
+  SocketAddress(const SocketAddress& addr);
+
+  // Resets to the nil address.
+  void Clear();
+
+  // Determines if this is a nil address (empty hostname, any IP, null port)
+  bool IsNil() const;
+
+  // Returns true if ip and port are set.
+  bool IsComplete() const;
+
+  // Replaces our address with the given one.
+  SocketAddress& operator=(const SocketAddress& addr);
+
+  // Changes the IP of this address to the given one, and clears the hostname.
+  void SetIP(uint32 ip);
+
+  // Changes the hostname of this address to the given one.
+  // Does not resolve the address; use Resolve to do so.
+  void SetIP(const std::string& hostname);
+
+  // Sets the IP address while retaining the hostname.  Useful for bypassing
+  // DNS for a pre-resolved IP.
+  void SetResolvedIP(uint32 ip);
+
+  // Changes the port of this address to the given one.
+  void SetPort(int port);
+
+  // Returns the hostname
+  const std::string& hostname() const { return hostname_; }
+
+  // Returns the IP address.
+  uint32 ip() const;
+
+  // Returns the port part of this address.
+  uint16 port() const;
+
+  // Returns the IP address in dotted form.
+  std::string IPAsString() const;
+
+  // Returns the port as a string
+  std::string PortAsString() const;
+
+  // Returns hostname:port
+  std::string ToString() const;
+
+  // Parses hostname:port
+  bool FromString(const std::string& str);
+
+  friend std::ostream& operator<<(std::ostream& os, const SocketAddress& addr);
+
+  // Determines whether this represents a missing / any IP address.  Hostname
+  // and/or port may be set.
+  bool IsAnyIP() const;
+  inline bool IsAny() const { return IsAnyIP(); }  // deprecated
+
+  // Determines whether the IP address refers to a loopback address, i.e. within
+  // the range 127.0.0.0/8.
+  bool IsLoopbackIP() const;
+
+  // Determines wither the IP address refers to any adapter on the local
+  // machine, including the loopback adapter.
+  bool IsLocalIP() const;
+
+  // Determines whether the IP address is in one of the private ranges:
+  // 127.0.0.0/8 10.0.0.0/8 192.168.0.0/16 172.16.0.0/12.
+  bool IsPrivateIP() const;
+
+  // Determines whether the hostname has been resolved to an IP.
+  bool IsUnresolvedIP() const;
+  inline bool IsUnresolved() const { return IsUnresolvedIP(); }  // deprecated
+
+  // Attempt to resolve a hostname to IP address.
+  // Returns false if resolution is required but failed, and sets error.
+  // 'force' will cause re-resolution of hostname.
+  bool ResolveIP(bool force = false, int* error = NULL);
+
+  // Determines whether this address is identical to the given one.
+  bool operator ==(const SocketAddress& addr) const;
+  inline bool operator !=(const SocketAddress& addr) const {
+    return !this->operator ==(addr);
+  }
+
+  // Compares based on IP and then port.
+  bool operator <(const SocketAddress& addr) const;
+
+  // Determines whether this address has the same IP as the one given.
+  bool EqualIPs(const SocketAddress& addr) const;
+
+  // Determines whether this address has the same port as the one given.
+  bool EqualPorts(const SocketAddress& addr) const;
+
+  // Hashes this address into a small number.
+  size_t Hash() const;
+
+  // Returns the size of this address when written.
+  size_t Size_() const;
+
+  // Writes this address into the given buffer, according to RFC 3489.
+  bool Write_(char* buf, int len) const;
+
+  // Reads this address from the given buffer, according to RFC 3489.
+  bool Read_(const char* buf, int len);
+
+  // Write this address to a sockaddr_in.
+  void ToSockAddr(sockaddr_in* saddr) const;
+
+  // Read this address from a sockaddr_in.
+  bool FromSockAddr(const sockaddr_in& saddr);
+
+  // Converts the IP address given in compact form into dotted form.
+  static std::string IPToString(uint32 ip);
+
+  // Converts the IP address given in dotted form into compact form.
+  // Only dotted names (A.B.C.D) are resolved.
+  static bool StringToIP(const std::string& str, uint32* ip);
+  static uint32 StringToIP(const std::string& str);  // deprecated
+
+  // Get local machine's hostname
+  static std::string GetHostname();
+
+  // Get a list of the local machine's ip addresses
+  static bool GetLocalIPs(std::vector<uint32>& ips);
+
+ private:
+  std::string hostname_;
+  uint32 ip_;
+  uint16 port_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_SOCKETADDRESS_H_
diff --git a/talk/base/socketaddresspair.cc b/talk/base/socketaddresspair.cc
new file mode 100644
index 0000000..7f190a9
--- /dev/null
+++ b/talk/base/socketaddresspair.cc
@@ -0,0 +1,58 @@
+/*
+ * 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/base/socketaddresspair.h"
+
+namespace talk_base {
+
+SocketAddressPair::SocketAddressPair(
+    const SocketAddress& src, const SocketAddress& dest)
+    : src_(src), dest_(dest) {
+}
+
+
+bool SocketAddressPair::operator ==(const SocketAddressPair& p) const {
+  return (src_ == p.src_) && (dest_ == p.dest_);
+}
+
+bool SocketAddressPair::operator <(const SocketAddressPair& p) const {
+  if (src_ < p.src_)
+    return true;
+  if (p.src_ < src_)
+    return false;
+  if (dest_ < p.dest_)
+    return true;
+  if (p.dest_ < dest_)
+    return false;
+  return false;
+}
+
+size_t SocketAddressPair::Hash() const {
+  return src_.Hash() ^ dest_.Hash();
+}
+
+} // namespace talk_base
diff --git a/talk/base/socketaddresspair.h b/talk/base/socketaddresspair.h
new file mode 100644
index 0000000..10f5d30
--- /dev/null
+++ b/talk/base/socketaddresspair.h
@@ -0,0 +1,58 @@
+/*
+ * 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_BASE_SOCKETADDRESSPAIR_H__
+#define TALK_BASE_SOCKETADDRESSPAIR_H__
+
+#include "talk/base/socketaddress.h"
+
+namespace talk_base {
+
+// Records a pair (source,destination) of socket addresses.  The two addresses
+// identify a connection between two machines.  (For UDP, this "connection" is
+// not maintained explicitly in a socket.)
+class SocketAddressPair {
+public:
+  SocketAddressPair() {}
+  SocketAddressPair(const SocketAddress& srs, const SocketAddress& dest);
+
+  const SocketAddress& source() const { return src_; }
+  const SocketAddress& destination() const { return dest_; }
+
+  bool operator ==(const SocketAddressPair& r) const;
+  bool operator <(const SocketAddressPair& r) const;
+
+  size_t Hash() const;
+
+private:
+  SocketAddress src_;
+  SocketAddress dest_;
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_SOCKETADDRESSPAIR_H__
diff --git a/talk/base/socketfactory.h b/talk/base/socketfactory.h
new file mode 100644
index 0000000..2a4aee2
--- /dev/null
+++ b/talk/base/socketfactory.h
@@ -0,0 +1,51 @@
+/*
+ * 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_BASE_SOCKETFACTORY_H__
+#define TALK_BASE_SOCKETFACTORY_H__
+
+#include "talk/base/socket.h"
+#include "talk/base/asyncsocket.h"
+
+namespace talk_base {
+
+class SocketFactory {
+public:
+  virtual ~SocketFactory() {}
+
+  // Returns a new socket for blocking communication.  The type can be
+  // SOCK_DGRAM and SOCK_STREAM.
+  virtual Socket* CreateSocket(int type) = 0;
+
+  // Returns a new socket for nonblocking communication.  The type can be
+  // SOCK_DGRAM and SOCK_STREAM.
+  virtual AsyncSocket* CreateAsyncSocket(int type) = 0;
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_SOCKETFACTORY_H__
diff --git a/talk/base/socketpool.cc b/talk/base/socketpool.cc
new file mode 100644
index 0000000..a5e3bcc
--- /dev/null
+++ b/talk/base/socketpool.cc
@@ -0,0 +1,292 @@
+/*
+ * 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 <iomanip>
+
+#include "talk/base/asyncsocket.h"
+#include "talk/base/logging.h"
+#include "talk/base/socketfactory.h"
+#include "talk/base/socketpool.h"
+#include "talk/base/socketstream.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamCache - Caches a set of open streams, defers creation to a separate
+//  StreamPool.
+///////////////////////////////////////////////////////////////////////////////
+
+StreamCache::StreamCache(StreamPool* pool) : pool_(pool) {
+}
+
+StreamCache::~StreamCache() {
+  for (ConnectedList::iterator it = active_.begin(); it != active_.end();
+       ++it) {
+    delete it->second;
+  }
+  for (ConnectedList::iterator it = cached_.begin(); it != cached_.end();
+       ++it) {
+    delete it->second;
+  }
+}
+
+StreamInterface* StreamCache::RequestConnectedStream(
+    const SocketAddress& remote, int* err) {
+  LOG_F(LS_VERBOSE) << "(" << remote << ")";
+  for (ConnectedList::iterator it = cached_.begin(); it != cached_.end();
+       ++it) {
+    if (remote == it->first) {
+      it->second->SignalEvent.disconnect(this);
+      // Move from cached_ to active_
+      active_.push_front(*it);
+      cached_.erase(it);
+      if (err)
+        *err = 0;
+      LOG_F(LS_VERBOSE) << "Providing cached stream";
+      return active_.front().second;
+    }
+  }
+  if (StreamInterface* stream = pool_->RequestConnectedStream(remote, err)) {
+    // We track active streams so that we can remember their address
+    active_.push_front(ConnectedStream(remote, stream));
+    LOG_F(LS_VERBOSE) << "Providing new stream";
+    return active_.front().second;
+  }
+  return NULL;
+}
+
+void StreamCache::ReturnConnectedStream(StreamInterface* stream) {
+  for (ConnectedList::iterator it = active_.begin(); it != active_.end();
+       ++it) {
+    if (stream == it->second) {
+      LOG_F(LS_VERBOSE) << "(" << it->first << ")";
+      if (stream->GetState() == SS_CLOSED) {
+        // Return closed streams
+        LOG_F(LS_VERBOSE) << "Returning closed stream";
+        pool_->ReturnConnectedStream(it->second);
+      } else {
+        // Monitor open streams
+        stream->SignalEvent.connect(this, &StreamCache::OnStreamEvent);
+        LOG_F(LS_VERBOSE) << "Caching stream";
+        cached_.push_front(*it);
+      }
+      active_.erase(it);
+      return;
+    }
+  }
+  ASSERT(false);
+}
+
+void StreamCache::OnStreamEvent(StreamInterface* stream, int events, int err) {
+  if ((events & SE_CLOSE) == 0) {
+    LOG_F(LS_WARNING) << "(" << events << ", " << err
+                      << ") received non-close event";
+    return;
+  }
+  for (ConnectedList::iterator it = cached_.begin(); it != cached_.end();
+       ++it) {
+    if (stream == it->second) {
+      LOG_F(LS_VERBOSE) << "(" << it->first << ")";
+      // We don't cache closed streams, so return it.
+      it->second->SignalEvent.disconnect(this);
+      LOG_F(LS_VERBOSE) << "Returning closed stream";
+      pool_->ReturnConnectedStream(it->second);
+      cached_.erase(it);
+      return;
+    }
+  }
+  ASSERT(false);
+}
+
+//////////////////////////////////////////////////////////////////////
+// NewSocketPool
+//////////////////////////////////////////////////////////////////////
+
+NewSocketPool::NewSocketPool(SocketFactory* factory) : factory_(factory) {
+}
+
+NewSocketPool::~NewSocketPool() {
+}
+
+StreamInterface* 
+NewSocketPool::RequestConnectedStream(const SocketAddress& remote, int* err) {
+  AsyncSocket* socket = factory_->CreateAsyncSocket(SOCK_STREAM);
+  if (!socket) {
+    ASSERT(false);
+    if (err)
+      *err = -1;
+    return NULL;
+  }
+  if ((socket->Connect(remote) != 0) && !socket->IsBlocking()) {
+    if (err)
+      *err = socket->GetError();
+    delete socket;
+    return NULL;
+  }
+  if (err)
+    *err = 0;
+  return new SocketStream(socket);
+}
+
+void
+NewSocketPool::ReturnConnectedStream(StreamInterface* stream) {
+  Thread::Current()->Dispose(stream);
+}
+
+//////////////////////////////////////////////////////////////////////
+// ReuseSocketPool
+//////////////////////////////////////////////////////////////////////
+
+ReuseSocketPool::ReuseSocketPool(SocketFactory* factory)
+: factory_(factory), stream_(NULL), checked_out_(false) {
+}
+
+ReuseSocketPool::~ReuseSocketPool() {
+  ASSERT(!checked_out_);
+  delete stream_;
+}
+
+StreamInterface* 
+ReuseSocketPool::RequestConnectedStream(const SocketAddress& remote, int* err) {
+  // Only one socket can be used from this "pool" at a time
+  ASSERT(!checked_out_);
+  if (!stream_) {
+    LOG_F(LS_VERBOSE) << "Creating new socket";
+    AsyncSocket* socket = factory_->CreateAsyncSocket(SOCK_STREAM);
+    if (!socket) {
+      ASSERT(false);
+      if (err)
+        *err = -1;
+      return NULL;
+    }
+    stream_ = new SocketStream(socket);
+  }
+  if ((stream_->GetState() == SS_OPEN) && (remote == remote_)) {
+    LOG_F(LS_VERBOSE) << "Reusing connection to: " << remote_;
+  } else {
+    remote_ = remote;
+    stream_->Close();
+    if ((stream_->GetSocket()->Connect(remote_) != 0)
+        && !stream_->GetSocket()->IsBlocking()) {
+      if (err)
+        *err = stream_->GetSocket()->GetError();
+      return NULL;
+    } else {
+      LOG_F(LS_VERBOSE) << "Opening connection to: " << remote_;
+    }
+  }
+  stream_->SignalEvent.disconnect(this);
+  checked_out_ = true;
+  if (err)
+    *err = 0;
+  return stream_;
+}
+
+void
+ReuseSocketPool::ReturnConnectedStream(StreamInterface* stream) {
+  ASSERT(stream == stream_);
+  ASSERT(checked_out_);
+  checked_out_ = false;
+  // Until the socket is reused, monitor it to determine if it closes.
+  stream_->SignalEvent.connect(this, &ReuseSocketPool::OnStreamEvent);
+}
+
+void
+ReuseSocketPool::OnStreamEvent(StreamInterface* stream, int events, int err) {
+  ASSERT(stream == stream_);
+  ASSERT(!checked_out_);
+
+  // If the stream was written to and then immediately returned to us then
+  // we may get a writable notification for it, which we should ignore.
+  if (events == SE_WRITE) {
+    LOG_F(LS_VERBOSE) << "Pooled Socket unexpectedly writable: ignoring";
+    return;
+  }
+
+  // If the peer sent data, we can't process it, so drop the connection.
+  // If the socket has closed, clean it up.
+  // In either case, we'll reconnect it the next time it is used.
+  ASSERT(0 != (events & (SE_READ|SE_CLOSE)));
+  if (0 != (events & SE_CLOSE)) {
+    LOG_F(LS_VERBOSE) << "Connection closed with error: " << err;
+  } else {
+    LOG_F(LS_VERBOSE) << "Pooled Socket unexpectedly readable: closing";
+  }
+  stream_->Close();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// LoggingPoolAdapter - Adapts a StreamPool to supply streams with attached
+// LoggingAdapters.
+///////////////////////////////////////////////////////////////////////////////
+
+LoggingPoolAdapter::LoggingPoolAdapter(
+    StreamPool* pool, LoggingSeverity level, const std::string& label,
+    bool binary_mode)
+  : pool_(pool), level_(level), label_(label), binary_mode_(binary_mode) {
+}
+
+LoggingPoolAdapter::~LoggingPoolAdapter() {
+  for (StreamList::iterator it = recycle_bin_.begin();
+       it != recycle_bin_.end(); ++it) {
+    delete *it;
+  }
+}
+
+StreamInterface* LoggingPoolAdapter::RequestConnectedStream(
+    const SocketAddress& remote, int* err) {
+  if (StreamInterface* stream = pool_->RequestConnectedStream(remote, err)) {
+    ASSERT(SS_CLOSED != stream->GetState());
+    std::stringstream ss;
+    ss << label_ << "(0x" << std::setfill('0') << std::hex << std::setw(8)
+       << stream << ")";
+    LOG_V(level_) << ss.str()
+                  << ((SS_OPEN == stream->GetState()) ? " Connected"
+                                                      : " Connecting")
+                  << " to " << remote;
+    if (recycle_bin_.empty()) {
+      return new LoggingAdapter(stream, level_, ss.str(), binary_mode_);
+    }
+    LoggingAdapter* logging = recycle_bin_.front();
+    recycle_bin_.pop_front();
+    logging->set_label(ss.str());
+    logging->Attach(stream);
+    return logging;
+  }
+  return NULL;
+}
+
+void LoggingPoolAdapter::ReturnConnectedStream(StreamInterface* stream) {
+  LoggingAdapter* logging = static_cast<LoggingAdapter*>(stream);
+  pool_->ReturnConnectedStream(logging->Detach());
+  recycle_bin_.push_back(logging);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
diff --git a/talk/base/socketpool.h b/talk/base/socketpool.h
new file mode 100644
index 0000000..847d8ff
--- /dev/null
+++ b/talk/base/socketpool.h
@@ -0,0 +1,160 @@
+/*
+ * 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_BASE_SOCKETPOOL_H_
+#define TALK_BASE_SOCKETPOOL_H_
+
+#include <deque>
+#include <list>
+#include "talk/base/logging.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/socketaddress.h"
+
+namespace talk_base {
+
+class AsyncSocket;
+class LoggingAdapter;
+class SocketFactory;
+class SocketStream;
+class StreamInterface;
+
+//////////////////////////////////////////////////////////////////////
+// StreamPool
+//////////////////////////////////////////////////////////////////////
+
+class StreamPool {
+public:
+  virtual ~StreamPool() { }
+
+  virtual StreamInterface* RequestConnectedStream(const SocketAddress& remote,
+                                                  int* err) = 0;
+  virtual void ReturnConnectedStream(StreamInterface* stream) = 0;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamCache - Caches a set of open streams, defers creation/destruction to
+//  the supplied StreamPool.
+///////////////////////////////////////////////////////////////////////////////
+
+class StreamCache : public StreamPool, public sigslot::has_slots<> {
+public:
+  StreamCache(StreamPool* pool);
+  virtual ~StreamCache();
+
+  // StreamPool Interface
+  virtual StreamInterface* RequestConnectedStream(const SocketAddress& remote,
+                                                  int* err);
+  virtual void ReturnConnectedStream(StreamInterface* stream);
+
+private:
+  typedef std::pair<SocketAddress, StreamInterface*> ConnectedStream;
+  typedef std::list<ConnectedStream> ConnectedList;
+
+  void OnStreamEvent(StreamInterface* stream, int events, int err);
+
+  // We delegate stream creation and deletion to this pool.
+  StreamPool* pool_;
+  // Streams that are in use (returned from RequestConnectedStream).
+  ConnectedList active_;
+  // Streams which were returned to us, but are still open.
+  ConnectedList cached_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// NewSocketPool
+// Creates a new stream on every request
+///////////////////////////////////////////////////////////////////////////////
+
+class NewSocketPool : public StreamPool {
+public:
+  NewSocketPool(SocketFactory* factory);
+  virtual ~NewSocketPool();
+  
+  // StreamPool Interface
+  virtual StreamInterface* RequestConnectedStream(const SocketAddress& remote,
+                                                  int* err);
+  virtual void ReturnConnectedStream(StreamInterface* stream);
+  
+private:
+  SocketFactory* factory_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// ReuseSocketPool
+// Maintains a single socket at a time, and will reuse it without closing if
+// the destination address is the same.
+///////////////////////////////////////////////////////////////////////////////
+
+class ReuseSocketPool : public StreamPool, public sigslot::has_slots<> {
+public:
+  ReuseSocketPool(SocketFactory* factory);
+  virtual ~ReuseSocketPool();
+
+  // StreamPool Interface
+  virtual StreamInterface* RequestConnectedStream(const SocketAddress& remote,
+                                                  int* err);
+  virtual void ReturnConnectedStream(StreamInterface* stream);
+  
+private:
+  void OnStreamEvent(StreamInterface* stream, int events, int err);
+
+  SocketFactory* factory_;
+  SocketStream* stream_;
+  SocketAddress remote_;
+  bool checked_out_;  // Whether the stream is currently checked out
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// LoggingPoolAdapter - Adapts a StreamPool to supply streams with attached
+// LoggingAdapters.
+///////////////////////////////////////////////////////////////////////////////
+
+class LoggingPoolAdapter : public StreamPool {
+public:
+  LoggingPoolAdapter(StreamPool* pool, LoggingSeverity level,
+                     const std::string& label, bool binary_mode);
+  virtual ~LoggingPoolAdapter();
+
+  // StreamPool Interface
+  virtual StreamInterface* RequestConnectedStream(const SocketAddress& remote,
+                                                  int* err);
+  virtual void ReturnConnectedStream(StreamInterface* stream);
+
+private:
+  StreamPool* pool_;
+  LoggingSeverity level_;
+  std::string label_;
+  bool binary_mode_;
+  typedef std::deque<LoggingAdapter*> StreamList;
+  StreamList recycle_bin_;
+};
+
+//////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_SOCKETPOOL_H_
diff --git a/talk/base/socketserver.h b/talk/base/socketserver.h
new file mode 100644
index 0000000..151ce61
--- /dev/null
+++ b/talk/base/socketserver.h
@@ -0,0 +1,61 @@
+/*
+ * 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_BASE_SOCKETSERVER_H_
+#define TALK_BASE_SOCKETSERVER_H_
+
+#include "talk/base/socketfactory.h"
+
+namespace talk_base {
+
+class MessageQueue;
+
+// Provides the ability to wait for activity on a set of sockets.  The Thread
+// class provides a nice wrapper on a socket server.
+//
+// The server is also a socket factory.  The sockets it creates will be
+// notified of asynchronous I/O from this server's Wait method.
+class SocketServer : public SocketFactory {
+ public:
+  // When the socket server is installed into a Thread, this function is
+  // called to allow the socket server to use the thread's message queue for
+  // any messaging that it might need to perform.
+  virtual void SetMessageQueue(MessageQueue* queue) {}
+
+  // Sleeps until:
+  //  1) cms milliseconds have elapsed (unless cms == kForever)
+  //  2) WakeUp() is called
+  // While sleeping, I/O is performed if process_io is true.
+  virtual bool Wait(int cms, bool process_io) = 0;
+
+  // Causes the current wait (if one is in progress) to wake up.
+  virtual void WakeUp() = 0;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_SOCKETSERVER_H_
diff --git a/talk/base/socketstream.cc b/talk/base/socketstream.cc
new file mode 100644
index 0000000..3dc5a95
--- /dev/null
+++ b/talk/base/socketstream.cc
@@ -0,0 +1,138 @@
+/*
+ * 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 "talk/base/socketstream.h"
+
+namespace talk_base {
+
+SocketStream::SocketStream(AsyncSocket* socket) : socket_(NULL) {
+  Attach(socket);
+}
+
+SocketStream::~SocketStream() {
+  delete socket_;
+}
+
+void SocketStream::Attach(AsyncSocket* socket) {
+  if (socket_)
+    delete socket_;
+  socket_ = socket;
+  if (socket_) {
+    socket_->SignalConnectEvent.connect(this, &SocketStream::OnConnectEvent);
+    socket_->SignalReadEvent.connect(this,    &SocketStream::OnReadEvent);
+    socket_->SignalWriteEvent.connect(this,   &SocketStream::OnWriteEvent);
+    socket_->SignalCloseEvent.connect(this,   &SocketStream::OnCloseEvent);
+  }
+}
+
+AsyncSocket* SocketStream::Detach() {
+  AsyncSocket* socket = socket_;
+  if (socket_) {
+    socket_->SignalConnectEvent.disconnect(this);
+    socket_->SignalReadEvent.disconnect(this);
+    socket_->SignalWriteEvent.disconnect(this);
+    socket_->SignalCloseEvent.disconnect(this);
+    socket_ = NULL;
+  }
+  return socket;
+}
+
+StreamState SocketStream::GetState() const {
+  ASSERT(socket_ != NULL);
+  switch (socket_->GetState()) {
+    case Socket::CS_CONNECTED:
+      return SS_OPEN;
+    case Socket::CS_CONNECTING:
+      return SS_OPENING;
+    case Socket::CS_CLOSED:
+    default:
+      return SS_CLOSED;
+  }
+}
+
+StreamResult SocketStream::Read(void* buffer, size_t buffer_len,
+                                size_t* read, int* error) {
+  ASSERT(socket_ != NULL);
+  int result = socket_->Recv(buffer, buffer_len);
+  if (result < 0) {
+    if (socket_->IsBlocking())
+      return SR_BLOCK;
+    if (error)
+      *error = socket_->GetError();
+    return SR_ERROR;
+  }
+  if ((result > 0) || (buffer_len == 0)) {
+    if (read)
+      *read = result;
+    return SR_SUCCESS;
+  }
+  return SR_EOS;
+}
+
+StreamResult SocketStream::Write(const void* data, size_t data_len,
+                                 size_t* written, int* error) {
+  ASSERT(socket_ != NULL);
+  int result = socket_->Send(data, data_len);
+  if (result < 0) {
+    if (socket_->IsBlocking())
+      return SR_BLOCK;
+    if (error)
+      *error = socket_->GetError();
+    return SR_ERROR;
+  }
+  if (written)
+    *written = result;
+  return SR_SUCCESS;
+}
+
+void SocketStream::Close() {
+  ASSERT(socket_ != NULL);
+  socket_->Close();
+}
+
+void SocketStream::OnConnectEvent(AsyncSocket* socket) {
+  ASSERT(socket == socket_);
+  SignalEvent(this, SE_OPEN | SE_READ | SE_WRITE, 0);
+}
+
+void SocketStream::OnReadEvent(AsyncSocket* socket) {
+  ASSERT(socket == socket_);
+  SignalEvent(this, SE_READ, 0);
+}
+
+void SocketStream::OnWriteEvent(AsyncSocket* socket) {
+  ASSERT(socket == socket_);
+  SignalEvent(this, SE_WRITE, 0);
+}
+
+void SocketStream::OnCloseEvent(AsyncSocket* socket, int err) {
+  ASSERT(socket == socket_);
+  SignalEvent(this, SE_CLOSE, err);
+}
+
+
+}  // namespace talk_base
diff --git a/talk/base/socketstream.h b/talk/base/socketstream.h
new file mode 100644
index 0000000..591dc4c
--- /dev/null
+++ b/talk/base/socketstream.h
@@ -0,0 +1,74 @@
+/*
+ * libjingle
+ * Copyright 2005--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.
+ */
+
+#ifndef TALK_BASE_SOCKETSTREAM_H_
+#define TALK_BASE_SOCKETSTREAM_H_
+
+#include "talk/base/asyncsocket.h"
+#include "talk/base/common.h"
+#include "talk/base/stream.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SocketStream : public StreamInterface, public sigslot::has_slots<> {
+ public:
+  explicit SocketStream(AsyncSocket* socket);
+  virtual ~SocketStream();
+
+  void Attach(AsyncSocket* socket);
+  AsyncSocket* Detach();
+
+  AsyncSocket* GetSocket() { return socket_; }
+
+  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:
+  void OnConnectEvent(AsyncSocket* socket);
+  void OnReadEvent(AsyncSocket* socket);
+  void OnWriteEvent(AsyncSocket* socket);
+  void OnCloseEvent(AsyncSocket* socket, int err);
+
+  AsyncSocket* socket_;
+
+  DISALLOW_EVIL_CONSTRUCTORS(SocketStream);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_SOCKETSTREAM_H_
diff --git a/talk/base/ssladapter.cc b/talk/base/ssladapter.cc
new file mode 100644
index 0000000..2368e7a
--- /dev/null
+++ b/talk/base/ssladapter.cc
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif  // HAVE_CONFIG_H
+
+// Decide which (if any) implementation of SSL we will use.
+#if !defined(SSL_USE_SCHANNEL) && !defined(SSL_USE_OPENSSL)
+#ifdef WIN32
+#define SSL_USE_SCHANNEL 1
+#else  // !WIN32
+#define SSL_USE_OPENSSL HAVE_OPENSSL_SSL_H
+#endif  // !WIN32
+#endif
+
+#include "talk/base/ssladapter.h"
+
+#if SSL_USE_SCHANNEL
+
+#include "schanneladapter.h"
+
+#elif SSL_USE_OPENSSL  // && !SSL_USE_SCHANNEL
+
+#include "openssladapter.h"
+
+#endif  // SSL_USE_OPENSSL && !SSL_USE_SCHANNEL
+
+///////////////////////////////////////////////////////////////////////////////
+
+namespace talk_base {
+
+SSLAdapter*
+SSLAdapter::Create(AsyncSocket* socket) {
+#if SSL_USE_SCHANNEL
+  return new SChannelAdapter(socket);
+#elif SSL_USE_OPENSSL  // && !SSL_USE_SCHANNEL
+  return new OpenSSLAdapter(socket);
+#else  // !SSL_USE_OPENSSL && !SSL_USE_SCHANNEL
+  return NULL;
+#endif  // !SSL_USE_OPENSSL && !SSL_USE_SCHANNEL
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#if SSL_USE_OPENSSL
+
+bool InitializeSSL(VerificationCallback callback) {
+  return OpenSSLAdapter::InitializeSSL(callback);
+}
+
+bool InitializeSSLThread() {
+  return OpenSSLAdapter::InitializeSSLThread();
+}
+
+bool CleanupSSL() {
+  return OpenSSLAdapter::CleanupSSL();
+}
+
+#else  // !SSL_USE_OPENSSL
+
+bool InitializeSSL(VerificationCallback callback) {
+  return true;
+}
+
+bool InitializeSSLThread() {
+  return true;
+}
+
+bool CleanupSSL() {
+  return true;
+}
+
+#endif  // !SSL_USE_OPENSSL
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
diff --git a/talk/base/ssladapter.h b/talk/base/ssladapter.h
new file mode 100644
index 0000000..687cc24
--- /dev/null
+++ b/talk/base/ssladapter.h
@@ -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.
+ */
+
+#ifndef TALK_BASE_SSLADAPTER_H__
+#define TALK_BASE_SSLADAPTER_H__
+
+#include "talk/base/asyncsocket.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SSLAdapter : public AsyncSocketAdapter {
+public:
+  SSLAdapter(AsyncSocket* socket)
+    : AsyncSocketAdapter(socket), ignore_bad_cert_(false) { }
+
+  bool ignore_bad_cert() const { return ignore_bad_cert_; }
+  void set_ignore_bad_cert(bool ignore) { ignore_bad_cert_ = ignore; }
+
+  // StartSSL returns 0 if successful.
+  // If StartSSL is called while the socket is closed or connecting, the SSL
+  // negotiation will begin as soon as the socket connects.
+  virtual int StartSSL(const char* hostname, bool restartable) = 0;
+
+  // Create the default SSL adapter for this platform
+  static SSLAdapter* Create(AsyncSocket* socket);
+
+private:
+  // If true, the server certificate need not match the configured hostname.
+  bool ignore_bad_cert_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+typedef bool (*VerificationCallback)(void* cert);
+
+// Call this on the main thread, before using SSL.
+// Call CleanupSSLThread when finished with SSL.
+bool InitializeSSL(VerificationCallback callback = NULL);
+
+// Call to initialize additional threads.
+bool InitializeSSLThread();
+
+// Call to cleanup additional threads, and also the main thread.
+bool CleanupSSL();
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif // TALK_BASE_SSLADAPTER_H__
diff --git a/talk/base/sslidentity.cc b/talk/base/sslidentity.cc
new file mode 100644
index 0000000..665e700
--- /dev/null
+++ b/talk/base/sslidentity.cc
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+
+// Handling of certificates and keypairs for SSLStreamAdapter's peer mode.
+
+#include <string>
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif  // HAVE_CONFIG_H
+
+// Decide which (if any) implementation of SSL we will use.
+#if !defined(SSL_USE_SCHANNEL) && !defined(SSL_USE_OPENSSL)
+#ifdef WIN32
+#define SSL_USE_SCHANNEL 1
+#else  // !WIN32
+#define SSL_USE_OPENSSL HAVE_OPENSSL_SSL_H
+#endif  // !WIN32
+#endif
+
+#include "talk/base/sslidentity.h"
+
+#if SSL_USE_SCHANNEL
+
+#error "Not implemented yet"
+
+#elif SSL_USE_OPENSSL  // && !SSL_USE_SCHANNEL
+
+#include "talk/base/opensslidentity.h"
+
+namespace talk_base {
+
+SSLCertificate* SSLCertificate::FromPEMString(const std::string& pem_string,
+                                              int* pem_length) {
+  return OpenSSLCertificate::FromPEMString(pem_string, pem_length);
+}
+
+SSLIdentity* SSLIdentity::Generate(const std::string& common_name) {
+  return OpenSSLIdentity::Generate(common_name);
+}
+
+}  // namespace talk_base
+
+#else  // !SSL_USE_OPENSSL && !SSL_USE_SCHANNEL
+
+#error "No SSL implementation"
+
+#endif  // SSL_USE_OPENSSL/!SSL_USE_SCHANNEL
diff --git a/talk/base/sslidentity.h b/talk/base/sslidentity.h
new file mode 100644
index 0000000..ed996b4
--- /dev/null
+++ b/talk/base/sslidentity.h
@@ -0,0 +1,91 @@
+/*
+ * 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.
+ */
+
+// Handling of certificates and keypairs for SSLStreamAdapter's peer mode.
+
+#ifndef TALK_BASE_SSLIDENTITY_H__
+#define TALK_BASE_SSLIDENTITY_H__
+
+#include <string>
+
+namespace talk_base {
+
+// Abstract interface overridden by SSL library specific
+// implementations.
+
+// A somewhat opaque type used to encapsulate a certificate.
+// Wraps the SSL library's notion of a certificate, with reference counting.
+// The SSLCertificate object is pretty much immutable once created.
+// (The OpenSSL implementation only does reference counting and
+// possibly caching of intermediate results.)
+class SSLCertificate {
+ public:
+  // Parses and build a certificate from a PEM encoded string.
+  // Returns NULL on failure.
+  // The length of the string representation of the certificate is
+  // stored in *pem_length if it is non-NULL, and only if
+  // parsing was successful.
+  // Caller is responsible for freeing the returned object.
+  static SSLCertificate* FromPEMString(const std::string& pem_string,
+                                             int* pem_length);
+  virtual ~SSLCertificate() {}
+
+  // Returns a new SSLCertificate object instance wrapping the same
+  // underlying certificate.
+  // Caller is responsible for freeing the returned object.
+  virtual SSLCertificate* GetReference() = 0;
+
+  // Returns a PEM encoded string representation of the certificate.
+  virtual std::string ToPEMString() const = 0;
+};
+
+// Our identity in an SSL negotiation: a keypair and certificate (both
+// with the same public key).
+// This too is pretty much immutable once created.
+class SSLIdentity {
+ public:
+  // Generates an identity (keypair and self-signed certificate). If
+  // common_name is non-empty, it will be used for the certificate's
+  // subject and issuer name, otherwise a random string will be used.
+  // Returns NULL on failure.
+  // Caller is responsible for freeing the returned object.
+  static SSLIdentity* Generate(const std::string& common_name);
+
+  virtual ~SSLIdentity() {}
+
+  // Returns a new SSLIdentity object instance wrapping the same
+  // identity information.
+  // Caller is responsible for freeing the returned object.
+  virtual SSLIdentity* GetReference() = 0;
+
+  // Returns a temporary reference to the certificate.
+  virtual SSLCertificate& certificate() const = 0;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_SSLIDENTITY_H__
diff --git a/talk/base/sslsocketfactory.cc b/talk/base/sslsocketfactory.cc
new file mode 100644
index 0000000..fcb2c0c
--- /dev/null
+++ b/talk/base/sslsocketfactory.cc
@@ -0,0 +1,181 @@
+/*
+ * libjingle
+ * Copyright 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/base/autodetectproxy.h"
+#include "talk/base/httpcommon.h"
+#include "talk/base/httpcommon-inl.h"
+#include "talk/base/socketadapters.h"
+#include "talk/base/ssladapter.h"
+#include "talk/base/sslsocketfactory.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// ProxySocketAdapter
+// TODO: Consider combining AutoDetectProxy and ProxySocketAdapter.  I think
+// the socket adapter is the more appropriate idiom for automatic proxy
+// detection.  We may or may not want to combine proxydetect.* as well.
+///////////////////////////////////////////////////////////////////////////////
+
+class ProxySocketAdapter : public AsyncSocketAdapter {
+ public:
+  ProxySocketAdapter(SslSocketFactory* factory, int type)
+      : AsyncSocketAdapter(NULL), factory_(factory), type_(type),
+        detect_(NULL) {
+  }
+  virtual ~ProxySocketAdapter() {
+    Close();
+  }
+
+  virtual int Connect(const SocketAddress& addr) {
+    ASSERT(NULL == detect_);
+    ASSERT(NULL == socket_);
+    remote_ = addr;
+    if (remote_.IsAnyIP() && remote_.hostname().empty()) {
+      LOG_F(LS_ERROR) << "Empty address";
+      return SOCKET_ERROR;
+    }
+    Url<char> url("/", remote_.IPAsString(), remote_.port());
+    detect_ = new AutoDetectProxy(factory_->agent_);
+    detect_->set_server_url(url.url());
+    detect_->SignalWorkDone.connect(this,
+        &ProxySocketAdapter::OnProxyDetectionComplete);
+    detect_->Start();
+    return SOCKET_ERROR;
+  }
+  virtual int GetError() const {
+    if (socket_) {
+      return socket_->GetError();
+    }
+    return detect_ ? EWOULDBLOCK : EADDRNOTAVAIL;
+  }
+  virtual int Close() {
+    if (socket_) {
+      return socket_->Close();
+    }
+    if (detect_) {
+      detect_->Destroy(false);
+      detect_ = NULL;
+    }
+    return 0;
+  }
+  virtual ConnState GetState() const {
+    if (socket_) {
+      return socket_->GetState();
+    }
+    return detect_ ? CS_CONNECTING : CS_CLOSED;
+  }
+
+private:
+  // AutoDetectProxy Slots
+  void OnProxyDetectionComplete(SignalThread* thread) {
+    ASSERT(detect_ == thread);
+    Attach(factory_->CreateProxySocket(detect_->proxy(), type_));
+    detect_->Release();
+    detect_ = NULL;
+    if (0 == AsyncSocketAdapter::Connect(remote_)) {
+      SignalConnectEvent(this);
+    } else if (!IsBlockingError(socket_->GetError())) {
+      SignalCloseEvent(this, socket_->GetError());
+    }
+  }
+
+  SslSocketFactory* factory_;
+  int type_;
+  SocketAddress remote_;
+  AutoDetectProxy* detect_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// SslSocketFactory
+///////////////////////////////////////////////////////////////////////////////
+
+Socket* SslSocketFactory::CreateSocket(int type) {
+  return factory_->CreateSocket(type);
+}
+
+AsyncSocket* SslSocketFactory::CreateAsyncSocket(int type) {
+  if (autodetect_proxy_) {
+    return new ProxySocketAdapter(this, type);
+  } else {
+    return CreateProxySocket(proxy_, type);
+  }
+}
+
+AsyncSocket* SslSocketFactory::CreateProxySocket(const ProxyInfo& proxy,
+                                                 int type) {
+  AsyncSocket* socket = factory_->CreateAsyncSocket(type);
+  if (!socket)
+    return NULL;
+
+  // Binary logging happens at the lowest level 
+  if (!logging_label_.empty() && binary_mode_) {
+    socket = new LoggingSocketAdapter(socket, logging_level_,
+                                      logging_label_.c_str(), binary_mode_);
+  }
+
+  if (proxy.type) {
+    AsyncSocket* proxy_socket = 0;
+    if (proxy_.type == PROXY_SOCKS5) {
+      proxy_socket = new AsyncSocksProxySocket(socket, proxy.address,
+                                               proxy.username, proxy.password);
+    } else {
+      // Note: we are trying unknown proxies as HTTPS currently
+      AsyncHttpsProxySocket* http_proxy =
+          new AsyncHttpsProxySocket(socket, agent_, proxy.address,
+                                    proxy.username, proxy.password);
+      http_proxy->SetForceConnect(force_connect_ || !hostname_.empty());
+      proxy_socket = http_proxy;
+    }
+    if (!proxy_socket) {
+      delete socket;
+      return NULL;
+    }
+    socket = proxy_socket;  // for our purposes the proxy is now the socket
+  }
+
+  if (!hostname_.empty()) {
+    if (SSLAdapter* ssl_adapter = SSLAdapter::Create(socket)) {
+      ssl_adapter->set_ignore_bad_cert(ignore_bad_cert_);
+      ssl_adapter->StartSSL(hostname_.c_str(), true);
+      socket = ssl_adapter;
+    } else {
+      LOG_F(LS_ERROR) << "SSL unavailable";
+    }
+  }
+
+  // Regular logging occurs at the highest level
+  if (!logging_label_.empty() && !binary_mode_) {
+    socket = new LoggingSocketAdapter(socket, logging_level_, 
+                                      logging_label_.c_str(), binary_mode_);
+  }
+  return socket;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
diff --git a/talk/base/sslsocketfactory.h b/talk/base/sslsocketfactory.h
new file mode 100644
index 0000000..d42689a
--- /dev/null
+++ b/talk/base/sslsocketfactory.h
@@ -0,0 +1,94 @@
+/*
+ * libjingle
+ * Copyright 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.
+ */
+
+#ifndef TALK_BASE_SSLSOCKETFACTORY_H__
+#define TALK_BASE_SSLSOCKETFACTORY_H__
+
+#include "talk/base/proxyinfo.h"
+#include "talk/base/socketserver.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// SslSocketFactory
+///////////////////////////////////////////////////////////////////////////////
+
+class SslSocketFactory : public SocketFactory {
+ public:
+  SslSocketFactory(SocketFactory* factory, const std::string& user_agent)
+     : factory_(factory), agent_(user_agent), autodetect_proxy_(true),
+       force_connect_(false), logging_level_(LS_VERBOSE), binary_mode_(false) {
+  }
+
+  void SetAutoDetectProxy() {
+    autodetect_proxy_ = true;
+  }
+  void SetForceConnect(bool force) {
+    force_connect_ = force;
+  }
+  void SetProxy(const ProxyInfo& proxy) {
+    autodetect_proxy_ = false;
+    proxy_ = proxy;
+  }
+  bool autodetect_proxy() const { return autodetect_proxy_; }
+  const ProxyInfo& proxy() const { return proxy_; }
+
+  void UseSSL(const char* hostname) { hostname_ = hostname; }
+  void DisableSSL() { hostname_.clear(); }
+  void SetIgnoreBadCert(bool ignore) { ignore_bad_cert_ = ignore; }
+  bool ignore_bad_cert() const { return ignore_bad_cert_; }
+
+  void SetLogging(LoggingSeverity level, const std::string& label, 
+                  bool binary_mode = false) {
+    logging_level_ = level;
+    logging_label_ = label;
+    binary_mode_ = binary_mode;
+  }
+
+  // SocketFactory Interface
+  virtual Socket* CreateSocket(int type);
+  virtual AsyncSocket* CreateAsyncSocket(int type);
+
+ private:
+  friend class ProxySocketAdapter;
+  AsyncSocket* CreateProxySocket(const ProxyInfo& proxy, int type);
+
+  SocketFactory* factory_;
+  std::string agent_;
+  bool autodetect_proxy_, force_connect_;
+  ProxyInfo proxy_;
+  std::string hostname_, logging_label_;
+  LoggingSeverity logging_level_;
+  bool binary_mode_;
+  bool ignore_bad_cert_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_SSLSOCKETFACTORY_H__
diff --git a/talk/base/sslstreamadapter.cc b/talk/base/sslstreamadapter.cc
new file mode 100644
index 0000000..17121fd
--- /dev/null
+++ b/talk/base/sslstreamadapter.cc
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif  // HAVE_CONFIG_H
+
+// Decide which (if any) implementation of SSL we will use.
+#if !defined(SSL_USE_SCHANNEL) && !defined(SSL_USE_OPENSSL)
+#ifdef WIN32
+#define SSL_USE_SCHANNEL 1
+#else  // !WIN32
+#define SSL_USE_OPENSSL HAVE_OPENSSL_SSL_H
+#endif  // !WIN32
+#endif
+
+#include "talk/base/sslstreamadapter.h"
+
+#if SSL_USE_SCHANNEL
+
+#error "Not implemented yet"
+
+#elif SSL_USE_OPENSSL  // && !SSL_USE_SCHANNEL
+
+#include "talk/base/opensslstreamadapter.h"
+
+#endif  // SSL_USE_OPENSSL && !SSL_USE_SCHANNEL
+
+///////////////////////////////////////////////////////////////////////////////
+
+namespace talk_base {
+
+SSLStreamAdapter* SSLStreamAdapter::Create(StreamInterface* stream) {
+#if SSL_USE_SCHANNEL
+  // not implemented yet
+  // return new SChannelStreamAdapter(stream);
+#elif SSL_USE_OPENSSL  // && !SSL_USE_SCHANNEL
+  return new OpenSSLStreamAdapter(stream);
+#else  // !SSL_USE_OPENSSL && !SSL_USE_SCHANNEL
+  return NULL;
+#endif  // !SSL_USE_OPENSSL && !SSL_USE_SCHANNEL
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
diff --git a/talk/base/sslstreamadapter.h b/talk/base/sslstreamadapter.h
new file mode 100644
index 0000000..62cef08
--- /dev/null
+++ b/talk/base/sslstreamadapter.h
@@ -0,0 +1,123 @@
+/*
+ * 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 TALK_BASE_SSLSTREAMADAPTER_H__
+#define TALK_BASE_SSLSTREAMADAPTER_H__
+
+#include "talk/base/stream.h"
+#include "talk/base/sslidentity.h"
+
+namespace talk_base {
+
+// SSLStreamAdapter : A StreamInterfaceAdapter that does SSL/TLS.
+// After SSL has been started, the stream will only open on successful
+// SSL verification of certificates, and the communication is
+// encrypted of course.
+//
+// This class was written with SSLAdapter as a starting point. It
+// offers a similar interface, with two differences: there is no
+// support for a restartable SSL connection, and this class has a
+// peer-to-peer mode.
+//
+// The SSL library requires initialization and cleanup. Static method
+// for doing this are in SSLAdapter. They should possibly be moved out
+// to a neutral class.
+
+class SSLStreamAdapter : public StreamAdapterInterface {
+ public:
+  // Instantiate an SSLStreamAdapter wrapping the given stream,
+  // (using the selected implementation for the platform).
+  // Caller is responsible for freeing the returned object.
+  static SSLStreamAdapter* Create(StreamInterface* stream);
+
+  explicit SSLStreamAdapter(StreamInterface* stream)
+      : StreamAdapterInterface(stream), ignore_bad_cert_(false) { }
+
+  void set_ignore_bad_cert(bool ignore) { ignore_bad_cert_ = ignore; }
+  bool ignore_bad_cert() const { return ignore_bad_cert_; }
+
+  // Specify our SSL identity: key and certificate. Mostly this is
+  // only used in the peer-to-peer mode (unless we actually want to
+  // provide a client certificate to a server).
+  // SSLStream takes ownership of the SSLIdentity object and will
+  // free it when appropriate. Should be called no more than once on a
+  // given SSLStream instance.
+  virtual void SetIdentity(SSLIdentity* identity) = 0;
+
+  // Call this to indicate that we are to play the server's role in
+  // the peer-to-peer mode.
+  virtual void SetServerRole() = 0;
+
+  // The mode of operation is selected by calling either
+  // StartSSLWithServer or StartSSLWithPeer.
+  // Use of the stream prior to calling either of these functions will
+  // pass data in clear text.
+  // Calling one of these functions causes SSL negotiation to begin as
+  // soon as possible: right away if the underlying wrapped stream is
+  // already opened, or else as soon as it opens.
+  //
+  // These functions return a negative error code on failure.
+  // Returning 0 means success so far, but negotiation is probably not
+  // complete and will continue asynchronously.  In that case, the
+  // exposed stream will open after successful negotiation and
+  // verification, or an SE_CLOSE event will be raised if negotiation
+  // fails.
+
+  // StartSSLWithServer starts SSL negotiation with a server in
+  // traditional mode. server_name specifies the expected server name
+  // which the server's certificate needs to specify.
+  virtual int StartSSLWithServer(const char* server_name) = 0;
+
+  // StartSSLWithPeer starts negotiation in the special peer-to-peer
+  // mode.
+  // Generally, SetIdentity() and possibly SetServerRole() should have
+  // been called before this. 
+  // SetPeerCertificate() must also be called. It may be called after
+  // StartSSLWithPeer() but must be called before the underlying
+  // stream opens.
+  virtual int StartSSLWithPeer() = 0;
+
+  // Specify the certificate that our peer is expected to use in
+  // peer-to-peer mode. Only this certificate will be accepted during
+  // SSL verification. The certificate is assumed to have been
+  // obtained through some other secure channel (such as the XMPP
+  // channel). (This could also specify the certificate authority that
+  // will sign the peer's certificate.)
+  // SSLStream takes ownership of the SSLCertificate object and will
+  // free it when appropriate. Should be called no more than once on a
+  // given SSLStream instance.
+  virtual void SetPeerCertificate(SSLCertificate* cert) = 0;
+
+  // If true, the server certificate need not match the configured
+  // server_name, and in fact missing certificate authority and other
+  // verification errors are ignored.
+  bool ignore_bad_cert_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_SSLSTREAMADAPTER_H__
diff --git a/talk/base/stream.cc b/talk/base/stream.cc
new file mode 100644
index 0000000..755fb08
--- /dev/null
+++ b/talk/base/stream.cc
@@ -0,0 +1,1094 @@
+/*
+ * 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.
+ */
+
+#if defined(POSIX)
+#include <sys/file.h>
+#endif  // POSIX
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <string>
+#include "talk/base/basictypes.h"
+#include "talk/base/common.h"
+#include "talk/base/messagequeue.h"
+#include "talk/base/stream.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/thread.h"
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#define fileno _fileno
+#endif
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamInterface
+///////////////////////////////////////////////////////////////////////////////
+
+enum {
+  MSG_POST_EVENT = 0xF1F1
+};
+
+StreamInterface::~StreamInterface() {
+}
+
+struct PostEventData : public MessageData {
+  int events, error;
+  PostEventData(int ev, int er) : events(ev), error(er) { }
+};
+
+StreamResult StreamInterface::WriteAll(const void* data, size_t data_len,
+                                       size_t* written, int* error) {
+  StreamResult result = SR_SUCCESS;
+  size_t total_written = 0, current_written;
+  while (total_written < data_len) {
+    result = Write(static_cast<const char*>(data) + total_written,
+                   data_len - total_written, &current_written, error);
+    if (result != SR_SUCCESS)
+      break;
+    total_written += current_written;
+  }
+  if (written)
+    *written = total_written;
+  return result;
+}
+
+StreamResult StreamInterface::ReadAll(void* buffer, size_t buffer_len,
+                                      size_t* read, int* error) {
+  StreamResult result = SR_SUCCESS;
+  size_t total_read = 0, current_read;
+  while (total_read < buffer_len) {
+    result = Read(static_cast<char*>(buffer) + total_read,
+                  buffer_len - total_read, &current_read, error);
+    if (result != SR_SUCCESS)
+      break;
+    total_read += current_read;
+  }
+  if (read)
+    *read = total_read;
+  return result;
+}
+
+StreamResult StreamInterface::ReadLine(std::string* line) {
+  line->clear();
+  StreamResult result = SR_SUCCESS;
+  while (true) {
+    char ch;
+    result = Read(&ch, sizeof(ch), NULL, NULL);
+    if (result != SR_SUCCESS) {
+      break;
+    }
+    if (ch == '\n') {
+      break;
+    }
+    line->push_back(ch);
+  }
+  if (!line->empty()) {   // give back the line we've collected so far with
+    result = SR_SUCCESS;  // a success code.  Otherwise return the last code
+  }
+  return result;
+}
+
+void StreamInterface::PostEvent(Thread* t, int events, int err) {
+  t->Post(this, MSG_POST_EVENT, new PostEventData(events, err));
+}
+
+void StreamInterface::PostEvent(int events, int err) {
+  PostEvent(Thread::Current(), events, err);
+}
+
+StreamInterface::StreamInterface() {
+}
+
+void StreamInterface::OnMessage(Message* msg) {
+  if (MSG_POST_EVENT == msg->message_id) {
+    PostEventData* pe = static_cast<PostEventData*>(msg->pdata);
+    SignalEvent(this, pe->events, pe->error);
+    delete msg->pdata;
+  }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamAdapterInterface
+///////////////////////////////////////////////////////////////////////////////
+
+StreamAdapterInterface::StreamAdapterInterface(StreamInterface* stream,
+                                               bool owned)
+    : stream_(stream), owned_(owned) {
+  if (NULL != stream_)
+    stream_->SignalEvent.connect(this, &StreamAdapterInterface::OnEvent);
+}
+
+void StreamAdapterInterface::Attach(StreamInterface* stream, bool owned) {
+  if (NULL != stream_)
+    stream_->SignalEvent.disconnect(this);
+  if (owned_)
+    delete stream_;
+  stream_ = stream;
+  owned_ = owned;
+  if (NULL != stream_)
+    stream_->SignalEvent.connect(this, &StreamAdapterInterface::OnEvent);
+}
+
+StreamInterface* StreamAdapterInterface::Detach() {
+  if (NULL != stream_)
+    stream_->SignalEvent.disconnect(this);
+  StreamInterface* stream = stream_;
+  stream_ = NULL;
+  return stream;
+}
+
+StreamAdapterInterface::~StreamAdapterInterface() {
+  if (owned_)
+    delete stream_;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamTap
+///////////////////////////////////////////////////////////////////////////////
+
+StreamTap::StreamTap(StreamInterface* stream, StreamInterface* tap)
+: StreamAdapterInterface(stream), tap_(NULL), tap_result_(SR_SUCCESS),
+  tap_error_(0)
+{
+  AttachTap(tap);
+}
+
+void StreamTap::AttachTap(StreamInterface* tap) {
+  tap_.reset(tap);
+}
+
+StreamInterface* StreamTap::DetachTap() {
+  return tap_.release();
+}
+
+StreamResult StreamTap::GetTapResult(int* error) {
+  if (error) {
+    *error = tap_error_;
+  }
+  return tap_result_;
+}
+
+StreamResult StreamTap::Read(void* buffer, size_t buffer_len,
+                             size_t* read, int* error) {
+  size_t backup_read;
+  if (!read) {
+    read = &backup_read;
+  }
+  StreamResult res = StreamAdapterInterface::Read(buffer, buffer_len,
+                                                  read, error);
+  if ((res == SR_SUCCESS) && (tap_result_ == SR_SUCCESS)) {
+    tap_result_ = tap_->WriteAll(buffer, *read, NULL, &tap_error_);
+  }
+  return res;
+}
+
+StreamResult StreamTap::Write(const void* data, size_t data_len,
+                              size_t* written, int* error) {
+  size_t backup_written;
+  if (!written) {
+    written = &backup_written;
+  }
+  StreamResult res = StreamAdapterInterface::Write(data, data_len,
+                                                   written, error);
+  if ((res == SR_SUCCESS) && (tap_result_ == SR_SUCCESS)) {
+    tap_result_ = tap_->WriteAll(data, *written, NULL, &tap_error_);
+  }
+  return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamSegment
+///////////////////////////////////////////////////////////////////////////////
+
+StreamSegment::StreamSegment(StreamInterface* stream)
+: StreamAdapterInterface(stream), start_(SIZE_UNKNOWN), pos_(0),
+  length_(SIZE_UNKNOWN)
+{
+  // It's ok for this to fail, in which case start_ is left as SIZE_UNKNOWN.
+  stream->GetPosition(&start_);
+}
+
+StreamSegment::StreamSegment(StreamInterface* stream, size_t length)
+: StreamAdapterInterface(stream), start_(SIZE_UNKNOWN), pos_(0),
+  length_(length)
+{
+  // It's ok for this to fail, in which case start_ is left as SIZE_UNKNOWN.
+  stream->GetPosition(&start_);
+}
+
+StreamResult StreamSegment::Read(void* buffer, size_t buffer_len,
+                                 size_t* read, int* error)
+{
+  if (SIZE_UNKNOWN != length_) {
+    if (pos_ >= length_)
+      return SR_EOS;
+    buffer_len = _min(buffer_len, length_ - pos_);
+  }
+  size_t backup_read;
+  if (!read) {
+    read = &backup_read;
+  }
+  StreamResult result = StreamAdapterInterface::Read(buffer, buffer_len,
+                                                     read, error);
+  if (SR_SUCCESS == result) {
+    pos_ += *read;
+  }
+  return result;
+}
+
+bool StreamSegment::SetPosition(size_t position) {
+  if (SIZE_UNKNOWN == start_)
+    return false;  // Not seekable
+  if ((SIZE_UNKNOWN != length_) && (position > length_))
+    return false;  // Seek past end of segment
+  if (!StreamAdapterInterface::SetPosition(start_ + position))
+    return false;
+  pos_ = position;
+  return true;
+}
+
+bool StreamSegment::GetPosition(size_t* position) const {
+  if (SIZE_UNKNOWN == start_)
+    return false;  // Not seekable
+  if (!StreamAdapterInterface::GetPosition(position))
+    return false;
+  if (position) {
+    ASSERT(*position >= start_);
+    *position -= start_;
+  }
+  return true;
+}
+
+bool StreamSegment::GetSize(size_t* size) const {
+  if (!StreamAdapterInterface::GetSize(size))
+    return false;
+  if (size) {
+    if (SIZE_UNKNOWN != start_) {
+      ASSERT(*size >= start_);
+      *size -= start_;
+    }
+    if (SIZE_UNKNOWN != length_) {
+      *size = _min(*size, length_);
+    }
+  }
+  return true;
+}
+
+bool StreamSegment::GetAvailable(size_t* size) const {
+  if (!StreamAdapterInterface::GetAvailable(size))
+    return false;
+  if (size && (SIZE_UNKNOWN != length_))
+    *size = _min(*size, length_ - pos_);
+  return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// NullStream
+///////////////////////////////////////////////////////////////////////////////
+
+NullStream::NullStream() {
+}
+
+NullStream::~NullStream() {
+}
+
+StreamState NullStream::GetState() const {
+  return SS_OPEN;
+}
+
+StreamResult NullStream::Read(void* buffer, size_t buffer_len,
+                              size_t* read, int* error) {
+  if (error) *error = -1;
+  return SR_ERROR;
+}
+
+StreamResult NullStream::Write(const void* data, size_t data_len,
+                               size_t* written, int* error) {
+  if (written) *written = data_len;
+  return SR_SUCCESS;
+}
+
+void NullStream::Close() {
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// FileStream
+///////////////////////////////////////////////////////////////////////////////
+
+FileStream::FileStream() : file_(NULL) {
+}
+
+FileStream::~FileStream() {
+  FileStream::Close();
+}
+
+bool FileStream::Open(const std::string& filename, const char* mode) {
+  Close();
+#ifdef WIN32
+  std::wstring wfilename;
+  if (Utf8ToWindowsFilename(filename, &wfilename)) {
+    file_ = _wfopen(wfilename.c_str(), ToUtf16(mode).c_str());
+  } else {
+    file_ = NULL;
+  }
+#else
+  file_ = fopen(filename.c_str(), mode);
+#endif
+  return (file_ != NULL);
+}
+
+bool FileStream::OpenShare(const std::string& filename, const char* mode,
+                           int shflag) {
+  Close();
+#ifdef WIN32
+  std::wstring wfilename;
+  if (Utf8ToWindowsFilename(filename, &wfilename)) {
+    file_ = _wfsopen(wfilename.c_str(), ToUtf16(mode).c_str(), shflag);
+  } else {
+    file_ = NULL;
+  }
+#else
+  return Open(filename, mode);
+#endif
+  return (file_ != NULL);
+}
+
+bool FileStream::DisableBuffering() {
+  if (!file_)
+    return false;
+  return (setvbuf(file_, NULL, _IONBF, 0) == 0);
+}
+
+StreamState FileStream::GetState() const {
+  return (file_ == NULL) ? SS_CLOSED : SS_OPEN;
+}
+
+StreamResult FileStream::Read(void* buffer, size_t buffer_len,
+                              size_t* read, int* error) {
+  if (!file_)
+    return SR_EOS;
+  size_t result = fread(buffer, 1, buffer_len, file_);
+  if ((result == 0) && (buffer_len > 0)) {
+    if (feof(file_))
+      return SR_EOS;
+    if (error)
+      *error = errno;
+    return SR_ERROR;
+  }
+  if (read)
+    *read = result;
+  return SR_SUCCESS;
+}
+
+StreamResult FileStream::Write(const void* data, size_t data_len,
+                               size_t* written, int* error) {
+  if (!file_)
+    return SR_EOS;
+  size_t result = fwrite(data, 1, data_len, file_);
+  if ((result == 0) && (data_len > 0)) {
+    if (error)
+      *error = errno;
+    return SR_ERROR;
+  }
+  if (written)
+    *written = result;
+  return SR_SUCCESS;
+}
+
+void FileStream::Close() {
+  if (file_) {
+    DoClose();
+    file_ = NULL;
+  }
+}
+
+bool FileStream::SetPosition(size_t position) {
+  if (!file_)
+    return false;
+  return (fseek(file_, position, SEEK_SET) == 0);
+}
+
+bool FileStream::GetPosition(size_t* position) const {
+  ASSERT(NULL != position);
+  if (!file_)
+    return false;
+  long result = ftell(file_);
+  if (result < 0)
+    return false;
+  if (position)
+    *position = result;
+  return true;
+}
+
+bool FileStream::GetSize(size_t* size) const {
+  ASSERT(NULL != size);
+  if (!file_)
+    return false;
+  struct stat file_stats;
+  if (fstat(fileno(file_), &file_stats) != 0)
+    return false;
+  if (size)
+    *size = file_stats.st_size;
+  return true;
+}
+
+bool FileStream::GetAvailable(size_t* size) const {
+  ASSERT(NULL != size);
+  if (!GetSize(size))
+    return false;
+  long result = ftell(file_);
+  if (result < 0)
+    return false;
+  if (size)
+    *size -= result;
+  return true;
+}
+
+bool FileStream::ReserveSize(size_t size) {
+  // TODO: extend the file to the proper length
+  return true;
+}
+
+bool FileStream::GetSize(const std::string& filename, size_t* size) {
+  struct stat file_stats;
+  if (stat(filename.c_str(), &file_stats) != 0)
+    return false;
+  *size = file_stats.st_size;
+  return true;
+}
+
+bool FileStream::Flush() {
+  if (file_) {
+    return (0 == fflush(file_));
+  }
+  // try to flush empty file?
+  ASSERT(false);
+  return false;
+}
+
+#if defined(POSIX)
+
+bool FileStream::TryLock() {
+  if (file_ == NULL) {
+    // Stream not open.
+    ASSERT(false);
+    return false;
+  }
+
+  return flock(fileno(file_), LOCK_EX|LOCK_NB) == 0;
+}
+
+bool FileStream::Unlock() {
+  if (file_ == NULL) {
+    // Stream not open.
+    ASSERT(false);
+    return false;
+  }
+
+  return flock(fileno(file_), LOCK_UN) == 0;
+}
+
+#endif
+
+void FileStream::DoClose() {
+  fclose(file_);
+}
+
+#ifdef POSIX
+
+// Have to identically rewrite the FileStream destructor or else it would call
+// the base class's Close() instead of the sub-class's.
+POpenStream::~POpenStream() {
+  POpenStream::Close();
+}
+
+bool POpenStream::Open(const std::string& subcommand, const char* mode) {
+  Close();
+  file_ = popen(subcommand.c_str(), mode);
+  return file_ != NULL;
+}
+
+bool POpenStream::OpenShare(const std::string& subcommand, const char* mode,
+                            int shflag) {
+  return Open(subcommand, mode);
+}
+
+void POpenStream::DoClose() {
+  wait_status_ = pclose(file_);
+}
+
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+// MemoryStream
+///////////////////////////////////////////////////////////////////////////////
+
+MemoryStreamBase::MemoryStreamBase()
+  : buffer_(NULL), buffer_length_(0), data_length_(0),
+    seek_position_(0) {
+}
+
+StreamState MemoryStreamBase::GetState() const {
+  return SS_OPEN;
+}
+
+StreamResult MemoryStreamBase::Read(void* buffer, size_t bytes,
+                                    size_t* bytes_read, int* error) {
+  if (seek_position_ >= data_length_) {
+    return SR_EOS;
+  }
+  size_t available = data_length_ - seek_position_;
+  if (bytes > available) {
+    // Read partial buffer
+    bytes = available;
+  }
+  memcpy(buffer, &buffer_[seek_position_], bytes);
+  seek_position_ += bytes;
+  if (bytes_read) {
+    *bytes_read = bytes;
+  }
+  return SR_SUCCESS;
+}
+
+StreamResult MemoryStreamBase::Write(const void* buffer, size_t bytes,
+                                     size_t* bytes_written, int* error) {
+  size_t available = buffer_length_ - seek_position_;
+  if (0 == available) {
+    // Increase buffer size to the larger of:
+    // a) new position rounded up to next 256 bytes
+    // b) double the previous length
+    size_t new_buffer_length = _max(((seek_position_ + bytes) | 0xFF) + 1,
+                                    buffer_length_ * 2);
+    StreamResult result = DoReserve(new_buffer_length, error);
+    if (SR_SUCCESS != result) {
+      return result;
+    }
+    ASSERT(buffer_length_ >= new_buffer_length);
+    available = buffer_length_ - seek_position_;
+  }
+
+  if (bytes > available) {
+    bytes = available;
+  }
+  memcpy(&buffer_[seek_position_], buffer, bytes);
+  seek_position_ += bytes;
+  if (data_length_ < seek_position_) {
+    data_length_ = seek_position_;
+  }
+  if (bytes_written) {
+    *bytes_written = bytes;
+  }
+  return SR_SUCCESS;
+}
+
+void MemoryStreamBase::Close() {
+  // nothing to do
+}
+
+bool MemoryStreamBase::SetPosition(size_t position) {
+  if (position > data_length_)
+    return false;
+  seek_position_ = position;
+  return true;
+}
+
+bool MemoryStreamBase::GetPosition(size_t *position) const {
+  if (position)
+    *position = seek_position_;
+  return true;
+}
+
+bool MemoryStreamBase::GetSize(size_t *size) const {
+  if (size)
+    *size = data_length_;
+  return true;
+}
+
+bool MemoryStreamBase::GetAvailable(size_t *size) const {
+  if (size)
+    *size = data_length_ - seek_position_;
+  return true;
+}
+
+bool MemoryStreamBase::ReserveSize(size_t size) {
+  return (SR_SUCCESS == DoReserve(size, NULL));
+}
+
+StreamResult MemoryStreamBase::DoReserve(size_t size, int* error) {
+  return (buffer_length_ >= size) ? SR_SUCCESS : SR_EOS;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+MemoryStream::MemoryStream()
+  : buffer_alloc_(NULL) {
+}
+
+MemoryStream::MemoryStream(const char* data)
+  : buffer_alloc_(NULL) {
+  SetData(data, strlen(data));
+}
+
+MemoryStream::MemoryStream(const void* data, size_t length)
+  : buffer_alloc_(NULL) {
+  SetData(data, length);
+}
+
+MemoryStream::~MemoryStream() {
+  delete [] buffer_alloc_;
+}
+
+void MemoryStream::SetData(const void* data, size_t length) {
+  data_length_ = buffer_length_ = length;
+  delete [] buffer_alloc_;
+  buffer_alloc_ = new char[buffer_length_ + kAlignment];
+  buffer_ = reinterpret_cast<char*>(ALIGNP(buffer_alloc_, kAlignment));
+  memcpy(buffer_, data, data_length_);
+  seek_position_ = 0;
+}
+
+StreamResult MemoryStream::DoReserve(size_t size, int* error) {
+  if (buffer_length_ >= size)
+    return SR_SUCCESS;
+
+  if (char* new_buffer_alloc = new char[size + kAlignment]) {
+    char* new_buffer = reinterpret_cast<char*>(
+        ALIGNP(new_buffer_alloc, kAlignment));
+    memcpy(new_buffer, buffer_, data_length_);
+    delete [] buffer_alloc_;
+    buffer_alloc_ = new_buffer_alloc;
+    buffer_ = new_buffer;
+    buffer_length_ = size;
+    return SR_SUCCESS;
+  }
+
+  if (error) {
+    *error = ENOMEM;
+  }
+  return SR_ERROR;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ExternalMemoryStream::ExternalMemoryStream() {
+}
+
+ExternalMemoryStream::ExternalMemoryStream(void* data, size_t length) {
+  SetData(data, length);
+}
+
+ExternalMemoryStream::~ExternalMemoryStream() {
+}
+
+void ExternalMemoryStream::SetData(void* data, size_t length) {
+  data_length_ = buffer_length_ = length;
+  buffer_ = static_cast<char*>(data);
+  seek_position_ = 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// FifoBuffer
+///////////////////////////////////////////////////////////////////////////////
+
+FifoBuffer::FifoBuffer(size_t size)
+    : state_(SS_OPEN), buffer_(new char[size]), buffer_length_(size),
+      data_length_(0), read_position_(0), owner_(Thread::Current()) {
+  // all events are done on the owner_ thread
+}
+
+FifoBuffer::~FifoBuffer() {
+}
+
+bool FifoBuffer::GetBuffered(size_t* size) const {
+  CritScope cs(&crit_);
+  *size = data_length_;
+  return true;
+}
+
+bool FifoBuffer::SetCapacity(size_t size) {
+  CritScope cs(&crit_);
+  if (data_length_ > size) {
+    return false;
+  }
+
+  if (size != buffer_length_) {
+    char* buffer = new char[size];
+    const size_t copy = data_length_;
+    const size_t tail_copy = _min(copy, buffer_length_ - read_position_);
+    memcpy(buffer, &buffer_[read_position_], tail_copy);
+    memcpy(buffer + tail_copy, &buffer_[0], copy - tail_copy);
+    buffer_.reset(buffer);
+    read_position_ = 0;
+    buffer_length_ = size;
+  }
+  return true;
+}
+
+StreamState FifoBuffer::GetState() const {
+  return state_;
+}
+
+StreamResult FifoBuffer::Read(void* buffer, size_t bytes,
+                              size_t* bytes_read, int* error) {
+  CritScope cs(&crit_);
+  const size_t available = data_length_;
+  if (0 == available) {
+    return (state_ != SS_CLOSED) ? SR_BLOCK : SR_EOS;
+  }
+
+  const bool was_writable = data_length_ < buffer_length_;
+  const size_t copy = _min(bytes, available);
+  const size_t tail_copy = _min(copy, buffer_length_ - read_position_);
+  char* const p = static_cast<char*>(buffer);
+  memcpy(p, &buffer_[read_position_], tail_copy);
+  memcpy(p + tail_copy, &buffer_[0], copy - tail_copy);
+  read_position_ = (read_position_ + copy) % buffer_length_;
+  data_length_ -= copy;
+  if (bytes_read) {
+    *bytes_read = copy;
+  }
+  // if we were full before, and now we're not, post an event
+  if (!was_writable && copy > 0) {
+    PostEvent(owner_, SE_WRITE, 0);
+  }
+
+  return SR_SUCCESS;
+}
+
+StreamResult FifoBuffer::Write(const void* buffer, size_t bytes,
+                               size_t* bytes_written, int* error) {
+  CritScope cs(&crit_);
+  if (state_ == SS_CLOSED) {
+    return SR_EOS;
+  }
+
+  const size_t available = buffer_length_ - data_length_;
+  if (0 == available) {
+    return SR_BLOCK;
+  }
+
+  const bool was_readable = (data_length_ > 0);
+  const size_t write_position = (read_position_ + data_length_)
+      % buffer_length_;
+  const size_t copy = _min(bytes, available);
+  const size_t tail_copy = _min(copy, buffer_length_ - write_position);
+  const char* const p = static_cast<const char*>(buffer);
+  memcpy(&buffer_[write_position], p, tail_copy);
+  memcpy(&buffer_[0], p + tail_copy, copy - tail_copy);
+  data_length_ += copy;
+  if (bytes_written) {
+    *bytes_written = copy;
+  }
+  // if we didn't have any data to read before, and now we do, post an event
+  if (!was_readable && copy > 0) {
+    PostEvent(owner_, SE_READ, 0);
+  }
+
+  return SR_SUCCESS;
+}
+
+void FifoBuffer::Close() {
+  CritScope cs(&crit_);
+  state_ = SS_CLOSED;
+}
+
+const void* FifoBuffer::GetReadData(size_t* size) {
+  CritScope cs(&crit_);
+  *size = (read_position_ + data_length_ <= buffer_length_) ?
+      data_length_ : buffer_length_ - read_position_;
+  return &buffer_[read_position_];
+}
+
+void FifoBuffer::ConsumeReadData(size_t size) {
+  CritScope cs(&crit_);
+  ASSERT(size <= data_length_);
+  const bool was_writable = data_length_ < buffer_length_;
+  read_position_ = (read_position_ + size) % buffer_length_;
+  data_length_ -= size;
+  if (!was_writable && size > 0) {
+    PostEvent(owner_, SE_WRITE, 0);
+  }
+}
+
+void* FifoBuffer::GetWriteBuffer(size_t* size) {
+  CritScope cs(&crit_);
+  if (state_ == SS_CLOSED) {
+    return NULL;
+  }
+
+  // if empty, reset the write position to the beginning, so we can get
+  // the biggest possible block
+  if (data_length_ == 0) {
+    read_position_ = 0;
+  }
+
+  const size_t write_position = (read_position_ + data_length_)
+      % buffer_length_;
+  *size = (write_position >= read_position_) ?
+      buffer_length_ - write_position : read_position_ - write_position;
+  return &buffer_[write_position];
+}
+
+void FifoBuffer::ConsumeWriteBuffer(size_t size) {
+  CritScope cs(&crit_);
+  ASSERT(size <= buffer_length_ - data_length_);
+  const bool was_readable = (data_length_ > 0);
+  data_length_ += size;
+  if (!was_readable && size > 0) {
+    PostEvent(owner_, SE_READ, 0);
+  }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// LoggingAdapter
+///////////////////////////////////////////////////////////////////////////////
+
+LoggingAdapter::LoggingAdapter(StreamInterface* stream, LoggingSeverity level,
+                               const std::string& label, bool hex_mode)
+: StreamAdapterInterface(stream), level_(level), hex_mode_(hex_mode)
+{
+  set_label(label);
+}
+
+void LoggingAdapter::set_label(const std::string& label) {
+  label_.assign("[");
+  label_.append(label);
+  label_.append("]");
+}
+
+StreamResult LoggingAdapter::Read(void* buffer, size_t buffer_len,
+                                  size_t* read, int* error) {
+  size_t local_read; if (!read) read = &local_read;
+  StreamResult result = StreamAdapterInterface::Read(buffer, buffer_len, read,
+                                                     error);
+  if (result == SR_SUCCESS) {
+    LogMultiline(level_, label_.c_str(), true, buffer, *read, hex_mode_, &lms_);
+  }
+  return result;
+}
+
+StreamResult LoggingAdapter::Write(const void* data, size_t data_len,
+                                   size_t* written, int* error) {
+  size_t local_written; if (!written) written = &local_written;
+  StreamResult result = StreamAdapterInterface::Write(data, data_len, written,
+                                                      error);
+  if (result == SR_SUCCESS) {
+    LogMultiline(level_, label_.c_str(), false, data, *written, hex_mode_,
+                 &lms_);
+  }
+  return result;
+}
+
+void LoggingAdapter::Close() {
+  LogMultiline(level_, label_.c_str(), false, NULL, 0, hex_mode_, &lms_);
+  LogMultiline(level_, label_.c_str(), true, NULL, 0, hex_mode_, &lms_);
+  LOG_V(level_) << label_ << " Closed locally";
+  StreamAdapterInterface::Close();
+}
+
+void LoggingAdapter::OnEvent(StreamInterface* stream, int events, int err) {
+  if (events & SE_OPEN) {
+    LOG_V(level_) << label_ << " Open";
+  } else if (events & SE_CLOSE) {
+    LogMultiline(level_, label_.c_str(), false, NULL, 0, hex_mode_, &lms_);
+    LogMultiline(level_, label_.c_str(), true, NULL, 0, hex_mode_, &lms_);
+    LOG_V(level_) << label_ << " Closed with error: " << err;
+  }
+  StreamAdapterInterface::OnEvent(stream, events, err);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// StringStream - Reads/Writes to an external std::string
+///////////////////////////////////////////////////////////////////////////////
+
+StringStream::StringStream(std::string& str)
+: str_(str), read_pos_(0), read_only_(false)
+{
+}
+
+StringStream::StringStream(const std::string& str)
+: str_(const_cast<std::string&>(str)), read_pos_(0), read_only_(true)
+{
+}
+
+StreamState StringStream::GetState() const {
+  return SS_OPEN;
+}
+
+StreamResult StringStream::Read(void* buffer, size_t buffer_len,
+                                      size_t* read, int* error) {
+  size_t available = _min(buffer_len, str_.size() - read_pos_);
+  if (!available)
+    return SR_EOS;
+  memcpy(buffer, str_.data() + read_pos_, available);
+  read_pos_ += available;
+  if (read)
+    *read = available;
+  return SR_SUCCESS;
+}
+
+StreamResult StringStream::Write(const void* data, size_t data_len,
+                                      size_t* written, int* error) {
+  if (read_only_) {
+    if (error) {
+      *error = -1;
+    }
+    return SR_ERROR;
+  }
+  str_.append(static_cast<const char*>(data),
+              static_cast<const char*>(data) + data_len);
+  if (written)
+    *written = data_len;
+  return SR_SUCCESS;
+}
+
+void StringStream::Close() {
+}
+
+bool StringStream::SetPosition(size_t position) {
+  if (position > str_.size())
+    return false;
+  read_pos_ = position;
+  return true;
+}
+
+bool StringStream::GetPosition(size_t* position) const {
+  if (position)
+    *position = read_pos_;
+  return true;
+}
+
+bool StringStream::GetSize(size_t* size) const {
+  if (size)
+    *size = str_.size();
+  return true;
+}
+
+bool StringStream::GetAvailable(size_t* size) const {
+  if (size)
+    *size = str_.size() - read_pos_;
+  return true;
+}
+
+bool StringStream::ReserveSize(size_t size) {
+  if (read_only_)
+    return false;
+  str_.reserve(size);
+  return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamReference
+///////////////////////////////////////////////////////////////////////////////
+
+StreamReference::StreamReference(StreamInterface* stream)
+    : StreamAdapterInterface(stream, false) {
+  // owner set to false so the destructor does not free the stream.
+  stream_ref_count_ = new StreamRefCount(stream);
+}
+
+StreamInterface* StreamReference::NewReference() {
+  stream_ref_count_->AddReference();
+  return new StreamReference(stream_ref_count_, stream());
+}
+
+StreamReference::~StreamReference() {
+  stream_ref_count_->Release();
+}
+
+StreamReference::StreamReference(StreamRefCount* stream_ref_count,
+                                 StreamInterface* stream)
+    : StreamAdapterInterface(stream, false),
+      stream_ref_count_(stream_ref_count) {
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+StreamResult Flow(StreamInterface* source,
+                  char* buffer, size_t buffer_len,
+                  StreamInterface* sink,
+                  size_t* data_len /* = NULL */) {
+  ASSERT(buffer_len > 0);
+
+  StreamResult result;
+  size_t count, read_pos, write_pos;
+  if (data_len) {
+    read_pos = *data_len;
+  } else {
+    read_pos = 0;
+  }
+
+  bool end_of_stream = false;
+  do {
+    // Read until buffer is full, end of stream, or error
+    while (!end_of_stream && (read_pos < buffer_len)) {
+      result = source->Read(buffer + read_pos, buffer_len - read_pos,
+                            &count, NULL);
+      if (result == SR_EOS) {
+        end_of_stream = true;
+      } else if (result != SR_SUCCESS) {
+        if (data_len) {
+          *data_len = read_pos;
+        }
+        return result;
+      } else {
+        read_pos += count;
+      }
+    }
+
+    // Write until buffer is empty, or error (including end of stream)
+    write_pos = 0;
+    while (write_pos < read_pos) {
+      result = sink->Write(buffer + write_pos, read_pos - write_pos,
+                           &count, NULL);
+      if (result != SR_SUCCESS) {
+        if (data_len) {
+          *data_len = read_pos - write_pos;
+          if (write_pos > 0) {
+            memmove(buffer, buffer + write_pos, *data_len);
+          }
+        }
+        return result;
+      }
+      write_pos += count;
+    }
+
+    read_pos = 0;
+  } while (!end_of_stream);
+
+  if (data_len) {
+    *data_len = 0;
+  }
+  return SR_SUCCESS;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
diff --git a/talk/base/stream.h b/talk/base/stream.h
new file mode 100644
index 0000000..a8d399d
--- /dev/null
+++ b/talk/base/stream.h
@@ -0,0 +1,720 @@
+/*
+ * libjingle
+ * Copyright 2004--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.
+ */
+
+#ifndef TALK_BASE_STREAM_H__
+#define TALK_BASE_STREAM_H__
+
+#include "talk/base/basictypes.h"
+#include "talk/base/criticalsection.h"
+#include "talk/base/logging.h"
+#include "talk/base/messagehandler.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sigslot.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamInterface is a generic asynchronous stream interface, supporting read,
+// write, and close operations, and asynchronous signalling of state changes.
+// The interface is designed with file, memory, and socket implementations in
+// mind.  Some implementations offer extended operations, such as seeking.
+///////////////////////////////////////////////////////////////////////////////
+
+// The following enumerations are declared outside of the StreamInterface
+// class for brevity in use.
+
+// The SS_OPENING state indicates that the stream will signal open or closed
+// in the future.
+enum StreamState { SS_CLOSED, SS_OPENING, SS_OPEN };
+
+// Stream read/write methods return this value to indicate various success
+// and failure conditions described below.
+enum StreamResult { SR_ERROR, SR_SUCCESS, SR_BLOCK, SR_EOS };
+
+// StreamEvents are used to asynchronously signal state transitionss.  The flags
+// may be combined.
+//  SE_OPEN: The stream has transitioned to the SS_OPEN state
+//  SE_CLOSE: The stream has transitioned to the SS_CLOSED state
+//  SE_READ: Data is available, so Read is likely to not return SR_BLOCK
+//  SE_WRITE: Data can be written, so Write is likely to not return SR_BLOCK
+enum StreamEvent { SE_OPEN = 1, SE_READ = 2, SE_WRITE = 4, SE_CLOSE = 8 };
+
+class Thread;
+
+class StreamInterface : public MessageHandler {
+ public:
+  virtual ~StreamInterface();
+
+  virtual StreamState GetState() const = 0;
+
+  // Read attempts to fill buffer of size buffer_len.  Write attempts to send
+  // data_len bytes stored in data.  The variables read and write are set only
+  // on SR_SUCCESS (see below).  Likewise, error is only set on SR_ERROR.
+  // Read and Write return a value indicating:
+  //  SR_ERROR: an error occurred, which is returned in a non-null error
+  //    argument.  Interpretation of the error requires knowledge of the
+  //    stream's concrete type, which limits its usefulness.
+  //  SR_SUCCESS: some number of bytes were successfully written, which is
+  //    returned in a non-null read/write argument.
+  //  SR_BLOCK: the stream is in non-blocking mode, and the operation would
+  //    block, or the stream is in SS_OPENING state.
+  //  SR_EOS: the end-of-stream has been reached, or the stream is in the
+  //    SS_CLOSED state.
+  virtual StreamResult Read(void* buffer, size_t buffer_len,
+                            size_t* read, int* error) = 0;
+  virtual StreamResult Write(const void* data, size_t data_len,
+                             size_t* written, int* error) = 0;
+  // Attempt to transition to the SS_CLOSED state.  SE_CLOSE will not be
+  // signalled as a result of this call.
+  virtual void Close() = 0;
+
+  // Streams may signal one or more StreamEvents to indicate state changes.
+  // The first argument identifies the stream on which the state change occured.
+  // The second argument is a bit-wise combination of StreamEvents.
+  // If SE_CLOSE is signalled, then the third argument is the associated error
+  // code.  Otherwise, the value is undefined.
+  // Note: Not all streams will support asynchronous event signalling.  However,
+  // SS_OPENING and SR_BLOCK returned from stream member functions imply that
+  // certain events will be raised in the future.
+  sigslot::signal3<StreamInterface*, int, int> SignalEvent;
+
+  // Like calling SignalEvent, but posts a message to the specified thread,
+  // which will call SignalEvent.  This helps unroll the stack and prevent
+  // re-entrancy.
+  void PostEvent(Thread* t, int events, int err);
+  // Like the aforementioned method, but posts to the current thread.
+  void PostEvent(int events, int err);
+
+  //
+  // OPTIONAL OPERATIONS
+  //
+  // Not all implementations will support the following operations.  In general,
+  // a stream will only support an operation if it reasonably efficient to do
+  // so.  For example, while a socket could buffer incoming data to support
+  // seeking, it will not do so.  Instead, a buffering stream adapter should
+  // be used.
+  //
+  // Even though several of these operations are related, you should
+  // always use whichever operation is most relevant.  For example, you may
+  // be tempted to use GetSize() and GetPosition() to deduce the result of
+  // GetAvailable().  However, a stream which is read-once may support the
+  // latter operation but not the former.
+  //
+
+  // The following four methods are used to avoid coping data multiple times.
+
+  // GetReadData returns a pointer to a buffer which is owned by the stream.
+  // The buffer contains data_len bytes.  NULL is returned if no data is
+  // available, or if the method fails.  If the caller processes the data, it
+  // must call ConsumeReadData with the number of processed bytes.  GetReadData
+  // does not require a matching call to ConsumeReadData if the data is not
+  // processed.  Read and ConsumeReadData invalidate the buffer returned by
+  // GetReadData.
+  virtual const void* GetReadData(size_t* data_len) { return NULL; }
+  virtual void ConsumeReadData(size_t used) {}
+
+  // GetWriteBuffer returns a pointer to a buffer which is owned by the stream.
+  // The buffer has a capacity of buf_len bytes.  NULL is returned if there is
+  // no buffer available, or if the method fails.  The call may write data to
+  // the buffer, and then call ConsumeWriteBuffer with the number of bytes
+  // written.  GetWriteBuffer does not require a matching call to
+  // ConsumeWriteData if no data is written.  Write, ForceWrite, and
+  // ConsumeWriteData invalidate the buffer returned by GetWriteBuffer.
+  // TODO: Allow the caller to specify a minimum buffer size.  If the specified
+  // amount of buffer is not yet available, return NULL and Signal SE_WRITE
+  // when it is available.  If the requested amount is too large, return an
+  // error.
+  virtual void* GetWriteBuffer(size_t* buf_len) { return NULL; }
+  virtual void ConsumeWriteBuffer(size_t used) {}
+
+  // Write data_len bytes found in data, circumventing any throttling which
+  // would could cause SR_BLOCK to be returned.  Returns true if all the data
+  // was written.  Otherwise, the method is unsupported, or an unrecoverable
+  // error occurred, and the error value is set.  This method should be used
+  // sparingly to write critical data which should not be throttled.  A stream
+  // which cannot circumvent its blocking constraints should not implement this
+  // method.
+  // NOTE: This interface is being considered experimentally at the moment.  It
+  // would be used by JUDP and BandwidthStream as a way to circumvent certain
+  // soft limits in writing.
+  //virtual bool ForceWrite(const void* data, size_t data_len, int* error) {
+  //  if (error) *error = -1;
+  //  return false;
+  //}
+
+  // Seek to a byte offset from the beginning of the stream.  Returns false if
+  // the stream does not support seeking, or cannot seek to the specified
+  // position.
+  virtual bool SetPosition(size_t position) { return false; }
+
+  // Get the byte offset of the current position from the start of the stream.
+  // Returns false if the position is not known.
+  virtual bool GetPosition(size_t* position) const { return false; }
+
+  // Get the byte length of the entire stream.  Returns false if the length
+  // is not known.
+  virtual bool GetSize(size_t* size) const { return false; }
+
+  // Return the number of Read()-able bytes remaining before end-of-stream.
+  // Returns false if not known.
+  virtual bool GetAvailable(size_t* size) const { return false; }
+
+  // Return the number of Write()-able bytes remaining before end-of-stream.
+  // Returns false if not known.
+  virtual bool GetWriteRemaining(size_t* size) const { return false; }
+
+  // Communicates the amount of data which will be written to the stream.  The
+  // stream may choose to preallocate memory to accomodate this data.  The
+  // stream may return false to indicate that there is not enough room (ie,
+  // Write will return SR_EOS/SR_ERROR at some point).  Note that calling this
+  // function should not affect the existing state of data in the stream.
+  virtual bool ReserveSize(size_t size) { return true; }
+
+  //
+  // CONVENIENCE METHODS
+  //
+  // These methods are implemented in terms of other methods, for convenience.
+  //
+
+  // Seek to the start of the stream.
+  inline bool Rewind() { return SetPosition(0); }
+
+  // WriteAll is a helper function which repeatedly calls Write until all the
+  // data is written, or something other than SR_SUCCESS is returned.  Note that
+  // unlike Write, the argument 'written' is always set, and may be non-zero
+  // on results other than SR_SUCCESS.  The remaining arguments have the
+  // same semantics as Write.
+  StreamResult WriteAll(const void* data, size_t data_len,
+                        size_t* written, int* error);
+
+  // Similar to ReadAll.  Calls Read until buffer_len bytes have been read, or
+  // until a non-SR_SUCCESS result is returned.  'read' is always set.
+  StreamResult ReadAll(void* buffer, size_t buffer_len,
+                       size_t* read, int* error);
+
+  // ReadLine is a helper function which repeatedly calls Read until it hits
+  // the end-of-line character, or something other than SR_SUCCESS.
+  // TODO: this is too inefficient to keep here.  Break this out into a buffered
+  // readline object or adapter
+  StreamResult ReadLine(std::string *line);
+
+ protected:
+  StreamInterface();
+
+  // MessageHandler Interface
+  virtual void OnMessage(Message* msg);
+
+ private:
+  DISALLOW_EVIL_CONSTRUCTORS(StreamInterface);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamAdapterInterface is a convenient base-class for adapting a stream.
+// By default, all operations are pass-through.  Override the methods that you
+// require adaptation.  Streams should really be upgraded to reference-counted.
+// In the meantime, use the owned flag to indicate whether the adapter should
+// own the adapted stream.
+///////////////////////////////////////////////////////////////////////////////
+
+class StreamAdapterInterface : public StreamInterface,
+                               public sigslot::has_slots<> {
+ public:
+  explicit StreamAdapterInterface(StreamInterface* stream, bool owned = true);
+
+  // Core Stream Interface
+  virtual StreamState GetState() const {
+    return stream_->GetState();
+  }
+  virtual StreamResult Read(void* buffer, size_t buffer_len,
+                            size_t* read, int* error) {
+    return stream_->Read(buffer, buffer_len, read, error);
+  }
+  virtual StreamResult Write(const void* data, size_t data_len,
+                             size_t* written, int* error) {
+    return stream_->Write(data, data_len, written, error);
+  }
+  virtual void Close() {
+    stream_->Close();
+  }
+
+  // Optional Stream Interface
+  /*  Note: Many stream adapters were implemented prior to this Read/Write
+      interface.  Therefore, a simple pass through of data in those cases may
+      be broken.  At a later time, we should do a once-over pass of all
+      adapters, and make them compliant with these interfaces, after which this
+      code can be uncommented.
+  virtual const void* GetReadData(size_t* data_len) {
+    return stream_->GetReadData(data_len);
+  }
+  virtual void ConsumeReadData(size_t used) {
+    stream_->ConsumeReadData(used);
+  }
+
+  virtual void* GetWriteBuffer(size_t* buf_len) {
+    return stream_->GetWriteBuffer(buf_len);
+  }
+  virtual void ConsumeWriteBuffer(size_t used) {
+    stream_->ConsumeWriteBuffer(used);
+  }
+  */
+
+  /*  Note: This interface is currently undergoing evaluation.
+  virtual bool ForceWrite(const void* data, size_t data_len, int* error) {
+    return stream_->ForceWrite(data, data_len, error);
+  }
+  */
+
+  virtual bool SetPosition(size_t position) {
+    return stream_->SetPosition(position);
+  }
+  virtual bool GetPosition(size_t* position) const {
+    return stream_->GetPosition(position);
+  }
+  virtual bool GetSize(size_t* size) const {
+    return stream_->GetSize(size);
+  }
+  virtual bool GetAvailable(size_t* size) const {
+    return stream_->GetAvailable(size);
+  }
+  virtual bool GetWriteRemaining(size_t* size) const {
+    return stream_->GetWriteRemaining(size);
+  }
+  virtual bool ReserveSize(size_t size) {
+    return stream_->ReserveSize(size);
+  }
+
+  void Attach(StreamInterface* stream, bool owned = true);
+  StreamInterface* Detach();
+
+ protected:
+  virtual ~StreamAdapterInterface();
+
+  // Note that the adapter presents itself as the origin of the stream events,
+  // since users of the adapter may not recognize the adapted object.
+  virtual void OnEvent(StreamInterface* stream, int events, int err) {
+    SignalEvent(this, events, err);
+  }
+  StreamInterface* stream() { return stream_; }
+
+ private:
+  StreamInterface* stream_;
+  bool owned_;
+  DISALLOW_EVIL_CONSTRUCTORS(StreamAdapterInterface);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamTap is a non-modifying, pass-through adapter, which copies all data
+// in either direction to the tap.  Note that errors or blocking on writing to
+// the tap will prevent further tap writes from occurring.
+///////////////////////////////////////////////////////////////////////////////
+
+class StreamTap : public StreamAdapterInterface {
+ public:
+  explicit StreamTap(StreamInterface* stream, StreamInterface* tap);
+
+  void AttachTap(StreamInterface* tap);
+  StreamInterface* DetachTap();
+  StreamResult GetTapResult(int* error);
+
+  // StreamAdapterInterface Interface
+  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);
+
+ private:
+  scoped_ptr<StreamInterface> tap_;
+  StreamResult tap_result_;
+  int tap_error_;
+  DISALLOW_EVIL_CONSTRUCTORS(StreamTap);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamSegment adapts a read stream, to expose a subset of the adapted
+// stream's data.  This is useful for cases where a stream contains multiple
+// documents concatenated together.  StreamSegment can expose a subset of
+// the data as an independent stream, including support for rewinding and
+// seeking.
+///////////////////////////////////////////////////////////////////////////////
+
+class StreamSegment : public StreamAdapterInterface {
+ public:
+  // The current position of the adapted stream becomes the beginning of the
+  // segment.  If a length is specified, it bounds the length of the segment.
+  explicit StreamSegment(StreamInterface* stream);
+  explicit StreamSegment(StreamInterface* stream, size_t length);
+
+  // StreamAdapterInterface Interface
+  virtual StreamResult Read(void* buffer, size_t buffer_len,
+                            size_t* read, int* error);
+  virtual bool SetPosition(size_t position);
+  virtual bool GetPosition(size_t* position) const;
+  virtual bool GetSize(size_t* size) const;
+  virtual bool GetAvailable(size_t* size) const;
+
+ private:
+  size_t start_, pos_, length_;
+  DISALLOW_EVIL_CONSTRUCTORS(StreamSegment);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// NullStream gives errors on read, and silently discards all written data.
+///////////////////////////////////////////////////////////////////////////////
+
+class NullStream : public StreamInterface {
+ public:
+  NullStream();
+  virtual ~NullStream();
+
+  // StreamInterface Interface
+  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();
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// FileStream is a simple implementation of a StreamInterface, which does not
+// support asynchronous notification.
+///////////////////////////////////////////////////////////////////////////////
+
+class FileStream : public StreamInterface {
+ public:
+  FileStream();
+  virtual ~FileStream();
+
+  // The semantics of filename and mode are the same as stdio's fopen
+  virtual bool Open(const std::string& filename, const char* mode);
+  virtual bool OpenShare(const std::string& filename, const char* mode,
+                         int shflag);
+
+  // By default, reads and writes are buffered for efficiency.  Disabling
+  // buffering causes writes to block until the bytes on disk are updated.
+  virtual bool DisableBuffering();
+
+  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();
+  virtual bool SetPosition(size_t position);
+  virtual bool GetPosition(size_t* position) const;
+  virtual bool GetSize(size_t* size) const;
+  virtual bool GetAvailable(size_t* size) const;
+  virtual bool ReserveSize(size_t size);
+
+  bool Flush();
+
+#if defined(POSIX)
+  // Tries to aquire an exclusive lock on the file.
+  // Use OpenShare(...) on win32 to get similar functionality.
+  bool TryLock();
+  bool Unlock();
+#endif
+
+  // Note: Deprecated in favor of Filesystem::GetFileSize().
+  static bool GetSize(const std::string& filename, size_t* size);
+
+ protected:
+  virtual void DoClose();
+
+  FILE* file_;
+
+ private:
+  DISALLOW_EVIL_CONSTRUCTORS(FileStream);
+};
+
+#ifdef POSIX
+// A FileStream that is actually not a file, but the output or input of a
+// sub-command. See "man 3 popen" for documentation of the underlying OS popen()
+// function.
+class POpenStream : public FileStream {
+ public:
+  POpenStream() : wait_status_(-1) {}
+  virtual ~POpenStream();
+
+  virtual bool Open(const std::string& subcommand, const char* mode);
+  // Same as Open(). shflag is ignored.
+  virtual bool OpenShare(const std::string& subcommand, const char* mode,
+                         int shflag);
+
+  // Returns the wait status from the last Close() of an Open()'ed stream, or
+  // -1 if no Open()+Close() has been done on this object. Meaning of the number
+  // is documented in "man 2 wait".
+  int GetWaitStatus() const { return wait_status_; }
+
+ protected:
+  virtual void DoClose();
+
+ private:
+  int wait_status_;
+};
+#endif  // POSIX
+
+///////////////////////////////////////////////////////////////////////////////
+// MemoryStream is a simple implementation of a StreamInterface over in-memory
+// data.  Data is read and written at the current seek position.  Reads return
+// end-of-stream when they reach the end of data.  Writes actually extend the
+// end of data mark.
+///////////////////////////////////////////////////////////////////////////////
+
+class MemoryStreamBase : public StreamInterface {
+ public:
+  virtual StreamState GetState() const;
+  virtual StreamResult Read(void* buffer, size_t bytes, size_t* bytes_read,
+                            int* error);
+  virtual StreamResult Write(const void* buffer, size_t bytes,
+                             size_t* bytes_written, int* error);
+  virtual void Close();
+  virtual bool SetPosition(size_t position);
+  virtual bool GetPosition(size_t* position) const;
+  virtual bool GetSize(size_t* size) const;
+  virtual bool GetAvailable(size_t* size) const;
+  virtual bool ReserveSize(size_t size);
+
+  char* GetBuffer() { return buffer_; }
+  const char* GetBuffer() const { return buffer_; }
+
+ protected:
+  MemoryStreamBase();
+
+  virtual StreamResult DoReserve(size_t size, int* error);
+
+  // Invariant: 0 <= seek_position <= data_length_ <= buffer_length_
+  char* buffer_;
+  size_t buffer_length_;
+  size_t data_length_;
+  size_t seek_position_;
+
+ private:
+  DISALLOW_EVIL_CONSTRUCTORS(MemoryStreamBase);
+};
+
+// MemoryStream dynamically resizes to accomodate written data.
+
+class MemoryStream : public MemoryStreamBase {
+ public:
+  MemoryStream();
+  explicit MemoryStream(const char* data);  // Calls SetData(data, strlen(data))
+  MemoryStream(const void* data, size_t length);  // Calls SetData(data, length)
+  virtual ~MemoryStream();
+
+  void SetData(const void* data, size_t length);
+
+ protected:
+  virtual StreamResult DoReserve(size_t size, int* error);
+  // Memory Streams are aligned for efficiency.
+  static const int kAlignment = 16;
+  char* buffer_alloc_;
+};
+
+// ExternalMemoryStream adapts an external memory buffer, so writes which would
+// extend past the end of the buffer will return end-of-stream.
+
+class ExternalMemoryStream : public MemoryStreamBase {
+ public:
+  ExternalMemoryStream();
+  ExternalMemoryStream(void* data, size_t length);
+  virtual ~ExternalMemoryStream();
+
+  void SetData(void* data, size_t length);
+};
+
+// FifoBuffer allows for efficient, thread-safe buffering of data between
+// writer and reader. As the data can wrap around the end of the buffer,
+// MemoryStreamBase can't help us here.
+
+class FifoBuffer : public StreamInterface {
+ public:
+  // Creates a FIFO buffer with the specified capacity.
+  explicit FifoBuffer(size_t length);
+  virtual ~FifoBuffer();
+  // Gets the amount of data currently readable from the buffer.
+  bool GetBuffered(size_t* data_len) const;
+  // Resizes the buffer to the specified capacity. Fails if data_length_ > size
+  bool SetCapacity(size_t length);
+
+  // StreamInterface methods
+  virtual StreamState GetState() const;
+  virtual StreamResult Read(void* buffer, size_t bytes,
+                            size_t* bytes_read, int* error);
+  virtual StreamResult Write(const void* buffer, size_t bytes,
+                             size_t* bytes_written, int* error);
+  virtual void Close();
+  virtual const void* GetReadData(size_t* data_len);
+  virtual void ConsumeReadData(size_t used);
+  virtual void* GetWriteBuffer(size_t *buf_len);
+  virtual void ConsumeWriteBuffer(size_t used);
+
+ private:
+  StreamState state_;  // keeps the opened/closed state of the stream
+  scoped_array<char> buffer_;  // the allocated buffer
+  size_t buffer_length_;  // size of the allocated buffer
+  size_t data_length_;  // amount of readable data in the buffer
+  size_t read_position_;  // offset to the readable data
+  Thread* owner_;  // stream callbacks are dispatched on this thread
+  mutable CriticalSection crit_;  // object lock
+  DISALLOW_EVIL_CONSTRUCTORS(FifoBuffer);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class LoggingAdapter : public StreamAdapterInterface {
+ public:
+  LoggingAdapter(StreamInterface* stream, LoggingSeverity level,
+                 const std::string& label, bool hex_mode = false);
+
+  void set_label(const std::string& label);
+
+  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();
+
+ protected:
+  virtual void OnEvent(StreamInterface* stream, int events, int err);
+
+ private:
+  LoggingSeverity level_;
+  std::string label_;
+  bool hex_mode_;
+  LogMultilineState lms_;
+
+  DISALLOW_EVIL_CONSTRUCTORS(LoggingAdapter);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// StringStream - Reads/Writes to an external std::string
+///////////////////////////////////////////////////////////////////////////////
+
+class StringStream : public StreamInterface {
+ public:
+  explicit StringStream(std::string& str);
+  explicit StringStream(const std::string& str);
+
+  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();
+  virtual bool SetPosition(size_t position);
+  virtual bool GetPosition(size_t* position) const;
+  virtual bool GetSize(size_t* size) const;
+  virtual bool GetAvailable(size_t* size) const;
+  virtual bool ReserveSize(size_t size);
+
+ private:
+  std::string& str_;
+  size_t read_pos_;
+  bool read_only_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamReference - A reference counting stream adapter
+///////////////////////////////////////////////////////////////////////////////
+
+// Keep in mind that the streams and adapters defined in this file are
+// not thread-safe, so this has limited uses.
+
+// A StreamRefCount holds the reference count and a pointer to the
+// wrapped stream. It deletes the wrapped stream when there are no
+// more references. We can then have multiple StreamReference
+// instances pointing to one StreamRefCount, all wrapping the same
+// stream.
+
+class StreamReference : public StreamAdapterInterface {
+  class StreamRefCount;
+ public:
+  // Constructor for the first reference to a stream
+  // Note: get more references through NewReference(). Use this
+  // constructor only once on a given stream.
+  explicit StreamReference(StreamInterface* stream);
+  StreamInterface* GetStream() { return stream(); }
+  StreamInterface* NewReference();
+  virtual ~StreamReference();
+
+ private:
+  class StreamRefCount {
+   public:
+    explicit StreamRefCount(StreamInterface* stream)
+        : stream_(stream), ref_count_(1) {
+    }
+    void AddReference() {
+      CritScope lock(&cs_);
+      ++ref_count_;
+    }
+    void Release() {
+      int ref_count;
+      {  // Atomic ops would have been a better fit here.
+        CritScope lock(&cs_);
+        ref_count = --ref_count_;
+      }
+      if (ref_count == 0) {
+        delete stream_;
+        delete this;
+      }
+    }
+   private:
+    StreamInterface* stream_;
+    int ref_count_;
+    CriticalSection cs_;
+    DISALLOW_EVIL_CONSTRUCTORS(StreamRefCount);
+  };
+
+  // Constructor for adding references
+  explicit StreamReference(StreamRefCount* stream_ref_count,
+                           StreamInterface* stream);
+
+  StreamRefCount* stream_ref_count_;
+  DISALLOW_EVIL_CONSTRUCTORS(StreamReference);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Flow attempts to move bytes from source to sink via buffer of size
+// buffer_len.  The function returns SR_SUCCESS when source reaches
+// end-of-stream (returns SR_EOS), and all the data has been written successful
+// to sink.  Alternately, if source returns SR_BLOCK or SR_ERROR, or if sink
+// returns SR_BLOCK, SR_ERROR, or SR_EOS, then the function immediately returns
+// with the unexpected StreamResult value.
+// data_len is the length of the valid data in buffer. in case of error
+// this is the data that read from source but can't move to destination.
+// as a pass in parameter, it indicates data in buffer that should move to sink
+StreamResult Flow(StreamInterface* source,
+                  char* buffer, size_t buffer_len,
+                  StreamInterface* sink, size_t* data_len = NULL);
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_STREAM_H__
diff --git a/talk/base/stringdigest.cc b/talk/base/stringdigest.cc
new file mode 100644
index 0000000..1f98124
--- /dev/null
+++ b/talk/base/stringdigest.cc
@@ -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.
+ */
+
+
+#include "talk/base/md5.h"
+#include "talk/base/stringdigest.h"
+#include "talk/base/stringencode.h"
+
+namespace talk_base {
+
+std::string MD5(const std::string& data) {
+  MD5_CTX ctx;
+  MD5Init(&ctx);
+  MD5Update(&ctx, const_cast<unsigned char *>(reinterpret_cast<const unsigned char *>(data.data())), static_cast<unsigned int>(data.size()));
+  unsigned char digest[16];
+  MD5Final(digest, &ctx);
+  std::string hex_digest;
+  for (int i=0; i<16; ++i) {
+    hex_digest += hex_encode(digest[i] >> 4);
+    hex_digest += hex_encode(digest[i] & 0xf);
+  }
+  return hex_digest;
+}
+
+}  // namespace talk_base
diff --git a/talk/base/stringdigest.h b/talk/base/stringdigest.h
new file mode 100644
index 0000000..d75d845
--- /dev/null
+++ b/talk/base/stringdigest.h
@@ -0,0 +1,47 @@
+/*
+ * 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_BASE_STRINGDIGEST_H__
+#define TALK_BASE_STRINGDIGEST_H__
+
+#include <string>
+
+namespace talk_base {
+
+//////////////////////////////////////////////////////////////////////
+// Message Digest Utilities
+//////////////////////////////////////////////////////////////////////
+
+// Compute the MD5 message digest of data, and return it in 
+std::string MD5(const std::string& data);
+
+//////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_STRINGDIGEST_H__
diff --git a/talk/base/stringencode.cc b/talk/base/stringencode.cc
new file mode 100644
index 0000000..7433bdd
--- /dev/null
+++ b/talk/base/stringencode.cc
@@ -0,0 +1,581 @@
+/*
+ * 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.
+ */
+
+#include "talk/base/stringencode.h"
+
+#include <cstdio>
+#include <cstdlib>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/common.h"
+#include "talk/base/stringutils.h"
+
+namespace talk_base {
+
+/////////////////////////////////////////////////////////////////////////////
+// String Encoding Utilities
+/////////////////////////////////////////////////////////////////////////////
+
+static const char HEX[] = "0123456789abcdef";
+
+char hex_encode(unsigned char val) {
+  ASSERT(val < 16);
+  return (val < 16) ? HEX[val] : '!';
+}
+
+bool hex_decode(char ch, unsigned char* val) {
+  if ((ch >= '0') && (ch <= '9')) {
+    *val = ch - '0';
+  } else if ((ch >= 'A') && (ch <= 'Z')) {
+    *val = (ch - 'A') + 10;
+  } else if ((ch >= 'a') && (ch <= 'z')) {
+    *val = (ch - 'a') + 10;
+  } else {
+    return false;
+  }
+  return true;
+}
+
+size_t escape(char * buffer, size_t buflen,
+              const char * source, size_t srclen,
+              const char * illegal, char escape) {
+  ASSERT(NULL != buffer);  // TODO: estimate output size
+  if (buflen <= 0)
+    return 0;
+
+  size_t srcpos = 0, bufpos = 0;
+  while ((srcpos < srclen) && (bufpos + 1 < buflen)) {
+    char ch = source[srcpos++];
+    if ((ch == escape) || ::strchr(illegal, ch)) {
+      if (bufpos + 2 >= buflen)
+        break;
+      buffer[bufpos++] = escape;
+    }
+    buffer[bufpos++] = ch;
+  }
+
+  buffer[bufpos] = '\0';
+  return bufpos;
+}
+
+size_t unescape(char * buffer, size_t buflen,
+                const char * source, size_t srclen,
+                char escape) {
+  ASSERT(NULL != buffer);  // TODO: estimate output size
+  if (buflen <= 0)
+    return 0;
+
+  size_t srcpos = 0, bufpos = 0;
+  while ((srcpos < srclen) && (bufpos + 1 < buflen)) {
+    char ch = source[srcpos++];
+    if ((ch == escape) && (srcpos < srclen)) {
+      ch = source[srcpos++];
+    }
+    buffer[bufpos++] = ch;
+  }
+  buffer[bufpos] = '\0';
+  return bufpos;
+}
+
+size_t encode(char * buffer, size_t buflen,
+              const char * source, size_t srclen,
+              const char * illegal, char escape) {
+  ASSERT(NULL != buffer);  // TODO: estimate output size
+  if (buflen <= 0)
+    return 0;
+
+  size_t srcpos = 0, bufpos = 0;
+  while ((srcpos < srclen) && (bufpos + 1 < buflen)) {
+    char ch = source[srcpos++];
+    if ((ch != escape) && !::strchr(illegal, ch)) {
+      buffer[bufpos++] = ch;
+    } else if (bufpos + 3 >= buflen) {
+      break;
+    } else {
+      buffer[bufpos+0] = escape;
+      buffer[bufpos+1] = hex_encode((static_cast<unsigned char>(ch) >> 4) & 0xF);
+      buffer[bufpos+2] = hex_encode((static_cast<unsigned char>(ch)     ) & 0xF);
+      bufpos += 3;
+    }
+  }
+  buffer[bufpos] = '\0';
+  return bufpos;
+}
+
+size_t decode(char * buffer, size_t buflen,
+              const char * source, size_t srclen,
+              char escape) {
+  if (buflen <= 0)
+    return 0;
+
+  unsigned char h1, h2;
+  size_t srcpos = 0, bufpos = 0;
+  while ((srcpos < srclen) && (bufpos + 1 < buflen)) {
+    char ch = source[srcpos++];
+    if ((ch == escape)
+        && (srcpos + 1 < srclen)
+        && hex_decode(source[srcpos], &h1)
+        && hex_decode(source[srcpos+1], &h2)) {
+      buffer[bufpos++] = (h1 << 4) | h2;
+      srcpos += 2;
+    } else {
+      buffer[bufpos++] = ch;
+    }
+  }
+  buffer[bufpos] = '\0';
+  return bufpos;
+}
+
+const char* unsafe_filename_characters() {
+  // It might be better to have a single specification which is the union of
+  // all operating systems, unless one system is overly restrictive.
+#ifdef WIN32
+  return "\\/:*?\"<>|";
+#else  // !WIN32
+  // TODO
+  ASSERT(false);
+  return "";
+#endif  // !WIN23
+}
+
+const unsigned char URL_UNSAFE  = 0x1; // 0-33 "#$%&+,/:;<=>?@[\]^`{|} 127
+const unsigned char XML_UNSAFE  = 0x2; // "&'<>
+const unsigned char HTML_UNSAFE = 0x2; // "&'<>
+
+//  ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 6 5 7 8 9 : ; < = > ?
+//@ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _
+//` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~
+
+const unsigned char ASCII_CLASS[128] = {
+  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  1,0,3,1,1,1,3,2,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,1,1,3,1,3,1,
+  1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,
+  1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,1,
+};
+
+size_t url_encode(char * buffer, size_t buflen,
+                  const char * source, size_t srclen) {
+  if (NULL == buffer)
+    return srclen * 3 + 1;
+  if (buflen <= 0)
+    return 0;
+
+  size_t srcpos = 0, bufpos = 0;
+  while ((srcpos < srclen) && (bufpos + 1 < buflen)) {
+    unsigned char ch = source[srcpos++];
+    if ((ch < 128) && (ASCII_CLASS[ch] & URL_UNSAFE)) {
+      if (bufpos + 3 >= buflen) {
+        break;
+      }
+      buffer[bufpos+0] = '%';
+      buffer[bufpos+1] = hex_encode((ch >> 4) & 0xF);
+      buffer[bufpos+2] = hex_encode((ch     ) & 0xF);
+      bufpos += 3;
+    } else {
+      buffer[bufpos++] = ch;
+    }
+  }
+  buffer[bufpos] = '\0';
+  return bufpos;
+}
+
+size_t url_decode(char * buffer, size_t buflen,
+                  const char * source, size_t srclen) {
+  if (NULL == buffer)
+    return srclen + 1;
+  if (buflen <= 0)
+    return 0;
+
+  unsigned char h1, h2;
+  size_t srcpos = 0, bufpos = 0;
+  while ((srcpos < srclen) && (bufpos + 1 < buflen)) {
+    unsigned char ch = source[srcpos++];
+    if (ch == '+') {
+      buffer[bufpos++] = ' ';
+    } else if ((ch == '%')
+               && (srcpos + 1 < srclen)
+               && hex_decode(source[srcpos], &h1)
+               && hex_decode(source[srcpos+1], &h2))
+    {
+      buffer[bufpos++] = (h1 << 4) | h2;
+      srcpos += 2;
+    } else {
+      buffer[bufpos++] = ch;
+    }
+  }
+  buffer[bufpos] = '\0';
+  return bufpos;
+}
+
+size_t utf8_decode(const char* source, size_t srclen, unsigned long* value) {
+  const unsigned char* s = reinterpret_cast<const unsigned char*>(source);
+  if ((s[0] & 0x80) == 0x00) {                    // Check s[0] == 0xxxxxxx
+    *value = s[0];
+    return 1;
+  }
+  if ((srclen < 2) || ((s[1] & 0xC0) != 0x80)) {  // Check s[1] != 10xxxxxx
+    return 0;
+  }
+  // Accumulate the trailer byte values in value16, and combine it with the
+  // relevant bits from s[0], once we've determined the sequence length.
+  unsigned long value16 = (s[1] & 0x3F);
+  if ((s[0] & 0xE0) == 0xC0) {                    // Check s[0] == 110xxxxx
+    *value = ((s[0] & 0x1F) << 6) | value16;
+    return 2;
+  }
+  if ((srclen < 3) || ((s[2] & 0xC0) != 0x80)) {  // Check s[2] != 10xxxxxx
+    return 0;
+  }
+  value16 = (value16 << 6) | (s[2] & 0x3F);
+  if ((s[0] & 0xF0) == 0xE0) {                    // Check s[0] == 1110xxxx
+    *value = ((s[0] & 0x0F) << 12) | value16;
+    return 3;
+  }
+  if ((srclen < 4) || ((s[3] & 0xC0) != 0x80)) {  // Check s[3] != 10xxxxxx
+    return 0;
+  }
+  value16 = (value16 << 6) | (s[3] & 0x3F);
+  if ((s[0] & 0xF8) == 0xF0) {                    // Check s[0] == 11110xxx
+    *value = ((s[0] & 0x07) << 18) | value16;
+    return 4;
+  }
+  return 0;
+}
+
+size_t utf8_encode(char* buffer, size_t buflen, unsigned long value) {
+  if ((value <= 0x7F) && (buflen >= 1)) {
+    buffer[0] = static_cast<unsigned char>(value);
+    return 1;
+  }
+  if ((value <= 0x7FF) && (buflen >= 2)) {
+    buffer[0] = 0xC0 | static_cast<unsigned char>(value >> 6);
+    buffer[1] = 0x80 | static_cast<unsigned char>(value & 0x3F);
+    return 2;
+  }
+  if ((value <= 0xFFFF) && (buflen >= 3)) {
+    buffer[0] = 0xE0 | static_cast<unsigned char>(value >> 12);
+    buffer[1] = 0x80 | static_cast<unsigned char>((value >> 6) & 0x3F);
+    buffer[2] = 0x80 | static_cast<unsigned char>(value & 0x3F);
+    return 3;
+  }
+  if ((value <= 0x1FFFFF) && (buflen >= 4)) {
+    buffer[0] = 0xF0 | static_cast<unsigned char>(value >> 18);
+    buffer[1] = 0x80 | static_cast<unsigned char>((value >> 12) & 0x3F);
+    buffer[2] = 0x80 | static_cast<unsigned char>((value >> 6) & 0x3F);
+    buffer[3] = 0x80 | static_cast<unsigned char>(value & 0x3F);
+    return 4;
+  }
+  return 0;
+}
+
+size_t html_encode(char * buffer, size_t buflen,
+                   const char * source, size_t srclen) {
+  ASSERT(NULL != buffer);  // TODO: estimate output size
+  if (buflen <= 0)
+    return 0;
+
+  size_t srcpos = 0, bufpos = 0;
+  while ((srcpos < srclen) && (bufpos + 1 < buflen)) {
+    unsigned char ch = source[srcpos];
+    if (ch < 128) {
+      srcpos += 1;
+      if (ASCII_CLASS[ch] & HTML_UNSAFE) {
+        const char * escseq = 0;
+        size_t esclen = 0;
+        switch (ch) {
+          case '<':  escseq = "&lt;";   esclen = 4; break;
+          case '>':  escseq = "&gt;";   esclen = 4; break;
+          case '\'': escseq = "&#39;";  esclen = 5; break;
+          case '\"': escseq = "&quot;"; esclen = 6; break;
+          case '&':  escseq = "&amp;";  esclen = 5; break;
+          default: ASSERT(false);
+        }
+        if (bufpos + esclen >= buflen) {
+          break;
+        }
+        memcpy(buffer + bufpos, escseq, esclen);
+        bufpos += esclen;
+      } else {
+        buffer[bufpos++] = ch;
+      }
+    } else {
+      // Largest value is 0x1FFFFF => &#2097151;  (10 characters)
+      char escseq[11];
+      unsigned long val;
+      if (size_t vallen = utf8_decode(&source[srcpos], srclen - srcpos, &val)) {
+        srcpos += vallen;
+      } else {
+        // Not a valid utf8 sequence, just use the raw character.
+        val = static_cast<unsigned char>(source[srcpos++]);
+      }
+      size_t esclen = sprintfn(escseq, ARRAY_SIZE(escseq), "&#%lu;", val);
+      if (bufpos + esclen >= buflen) {
+        break;
+      }
+      memcpy(buffer + bufpos, escseq, esclen);
+      bufpos += esclen;
+    }
+  }
+  buffer[bufpos] = '\0';
+  return bufpos;
+}
+
+size_t html_decode(char * buffer, size_t buflen,
+                   const char * source, size_t srclen) {
+  ASSERT(NULL != buffer);  // TODO: estimate output size
+  return xml_decode(buffer, buflen, source, srclen);
+}
+
+size_t xml_encode(char * buffer, size_t buflen,
+                  const char * source, size_t srclen) {
+  ASSERT(NULL != buffer);  // TODO: estimate output size
+  if (buflen <= 0)
+    return 0;
+
+  size_t srcpos = 0, bufpos = 0;
+  while ((srcpos < srclen) && (bufpos + 1 < buflen)) {
+    unsigned char ch = source[srcpos++];
+    if ((ch < 128) && (ASCII_CLASS[ch] & XML_UNSAFE)) {
+      const char * escseq = 0;
+      size_t esclen = 0;
+      switch (ch) {
+        case '<':  escseq = "&lt;";   esclen = 4; break;
+        case '>':  escseq = "&gt;";   esclen = 4; break;
+        case '\'': escseq = "&apos;"; esclen = 6; break;
+        case '\"': escseq = "&quot;"; esclen = 6; break;
+        case '&':  escseq = "&amp;";  esclen = 5; break;
+        default: ASSERT(false);
+      }
+      if (bufpos + esclen >= buflen) {
+        break;
+      }
+      memcpy(buffer + bufpos, escseq, esclen);
+      bufpos += esclen;
+    } else {
+      buffer[bufpos++] = ch;
+    }
+  }
+  buffer[bufpos] = '\0';
+  return bufpos;
+}
+
+size_t xml_decode(char * buffer, size_t buflen,
+                  const char * source, size_t srclen) {
+  ASSERT(NULL != buffer);  // TODO: estimate output size
+  if (buflen <= 0)
+    return 0;
+
+  size_t srcpos = 0, bufpos = 0;
+  while ((srcpos < srclen) && (bufpos + 1 < buflen)) {
+    unsigned char ch = source[srcpos++];
+    if (ch != '&') {
+      buffer[bufpos++] = ch;
+    } else if ((srcpos + 2 < srclen)
+               && (memcmp(source + srcpos, "lt;", 3) == 0)) {
+      buffer[bufpos++] = '<';
+      srcpos += 3;
+    } else if ((srcpos + 2 < srclen)
+               && (memcmp(source + srcpos, "gt;", 3) == 0)) {
+      buffer[bufpos++] = '>';
+      srcpos += 3;
+    } else if ((srcpos + 4 < srclen)
+               && (memcmp(source + srcpos, "apos;", 5) == 0)) {
+      buffer[bufpos++] = '\'';
+      srcpos += 5;
+    } else if ((srcpos + 4 < srclen)
+               && (memcmp(source + srcpos, "quot;", 5) == 0)) {
+      buffer[bufpos++] = '\"';
+      srcpos += 5;
+    } else if ((srcpos + 3 < srclen)
+               && (memcmp(source + srcpos, "amp;", 4) == 0)) {
+      buffer[bufpos++] = '&';
+      srcpos += 4;
+    } else if ((srcpos < srclen) && (source[srcpos] == '#')) {
+      int int_base = 10;
+      if ((srcpos + 1 < srclen) && (source[srcpos+1] == 'x')) {
+        int_base = 16;
+        srcpos += 1;
+      }
+      char * ptr;
+      // TODO: Fix hack (ptr may go past end of data)
+      unsigned long val = strtoul(source + srcpos + 1, &ptr, int_base);
+      if ((static_cast<size_t>(ptr - source) < srclen) && (*ptr == ';')) {
+        srcpos = ptr - source + 1;
+      } else {
+        // Not a valid escape sequence.
+        break;
+      }
+      if (size_t esclen = utf8_encode(buffer + bufpos, buflen - bufpos, val)) {
+        bufpos += esclen;
+      } else {
+        // Not enough room to encode the character, or illegal character
+        break;
+      }
+    } else {
+      // Unrecognized escape sequence.
+      break;
+    }
+  }
+  buffer[bufpos] = '\0';
+  return bufpos;
+}
+
+std::string hex_encode(const char * source, size_t srclen) {
+  const size_t kBufferSize = srclen * 2 + 1;
+  char* buffer = STACK_ARRAY(char, kBufferSize);
+  size_t length = hex_encode(buffer, kBufferSize, source, srclen);
+  return std::string(buffer, length);
+}
+
+size_t hex_encode(char * buffer, size_t buflen,
+                  const char * csource, size_t srclen) {
+  ASSERT(NULL != buffer);  // TODO: estimate output size
+  if (buflen <= 0)
+    return 0;
+
+  const unsigned char * bsource =
+    reinterpret_cast<const unsigned char *>(csource);
+
+  size_t srcpos = 0, bufpos = 0;
+  srclen = _min(srclen, (buflen - 1) / 2);
+  while (srcpos < srclen) {
+    unsigned char ch = bsource[srcpos++];
+    buffer[bufpos  ] = hex_encode((ch >> 4) & 0xF);
+    buffer[bufpos+1] = hex_encode((ch     ) & 0xF);
+    bufpos += 2;
+  }
+  buffer[bufpos] = '\0';
+  return bufpos;
+}
+
+size_t hex_decode(char * cbuffer, size_t buflen,
+                  const char * source, size_t srclen) {
+  ASSERT(NULL != cbuffer);  // TODO: estimate output size
+  if (buflen <= 0)
+    return 0;
+
+  unsigned char * bbuffer = reinterpret_cast<unsigned char *>(cbuffer);
+
+  unsigned char h1, h2;
+  size_t srcpos = 0, bufpos = 0;
+  while ((srcpos + 1 < srclen)
+         && (bufpos + 1 < buflen)
+         && hex_decode(source[srcpos], &h1)
+         && hex_decode(source[srcpos+1], &h2))
+  {
+    bbuffer[bufpos++] = (h1 << 4) | h2;
+    srcpos += 2;
+  }
+  bbuffer[bufpos] = '\0';
+  return bufpos;
+}
+
+size_t transform(std::string& value, size_t maxlen, const std::string& source,
+                 Transform t) {
+  char* buffer = STACK_ARRAY(char, maxlen + 1);
+  size_t length = t(buffer, maxlen + 1, source.data(), source.length());
+  value.assign(buffer, length);
+  return length;
+}
+
+std::string s_transform(const std::string& source, Transform t) {
+  // Ask transformation function to approximate the destination size (returns upper bound)
+  size_t maxlen = t(NULL, 0, source.data(), source.length());
+  char * buffer = STACK_ARRAY(char, maxlen);
+  size_t len = t(buffer, maxlen, source.data(), source.length());
+  std::string result(buffer, len);
+  return result;
+}
+
+size_t tokenize(const std::string& source, char delimiter,
+                std::vector<std::string>* fields) {
+  ASSERT(NULL != fields);
+  fields->clear();
+  size_t last = 0;
+  for (size_t i = 0; i < source.length(); ++i) {
+    if (source[i] == delimiter) {
+      if (i != last) {
+        fields->push_back(source.substr(last, i - last));
+      }
+      last = i + 1;
+    }
+  }
+  if (last != source.length()) {
+    fields->push_back(source.substr(last, source.length() - last));
+  }
+  return fields->size();
+}
+
+size_t split(const std::string& source, char delimiter,
+             std::vector<std::string>* fields) {
+  ASSERT(NULL != fields);
+  fields->clear();
+  size_t last = 0;
+  for (size_t i = 0; i < source.length(); ++i) {
+    if (source[i] == delimiter) {
+      fields->push_back(source.substr(last, i - last));
+      last = i + 1;
+    }
+  }
+  fields->push_back(source.substr(last, source.length() - last));
+  return fields->size();
+}
+
+char make_char_safe_for_filename(char c) {
+  if (c < 32)
+    return '_';
+
+  switch (c) {
+    case '<':
+    case '>':
+    case ':':
+    case '"':
+    case '/':
+    case '\\':
+    case '|':
+    case '*':
+    case '?':
+      return '_';
+
+    default:
+      return c;
+  }
+}
+
+/*
+void sprintf(std::string& value, size_t maxlen, const char * format, ...) {
+  char * buffer = STACK_ARRAY(char, maxlen + 1);
+  va_list args;
+  va_start(args, format);
+  value.assign(buffer, vsprintfn(buffer, maxlen + 1, format, args));
+  va_end(args);
+}
+*/
+
+/////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
diff --git a/talk/base/stringencode.h b/talk/base/stringencode.h
new file mode 100644
index 0000000..fd29f87
--- /dev/null
+++ b/talk/base/stringencode.h
@@ -0,0 +1,184 @@
+/*
+ * 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_BASE_STRINGENCODE_H__
+#define TALK_BASE_STRINGENCODE_H__
+
+#include <string>
+#include <sstream>
+#include <vector>
+
+namespace talk_base {
+
+//////////////////////////////////////////////////////////////////////
+// String Encoding Utilities
+//////////////////////////////////////////////////////////////////////
+
+// Convert an unsigned value from 0 to 15 to the hex character equivalent...
+char hex_encode(unsigned char val);
+// ...and vice-versa.
+bool hex_decode(char ch, unsigned char* val);
+
+// Convert an unsigned value to it's utf8 representation.  Returns the length
+// of the encoded string, or 0 if the encoding is longer than buflen - 1.
+size_t utf8_encode(char* buffer, size_t buflen, unsigned long value);
+// Decode the utf8 encoded value pointed to by source.  Returns the number of
+// bytes used by the encoding, or 0 if the encoding is invalid.
+size_t utf8_decode(const char* source, size_t srclen, unsigned long* value);
+
+// Escaping prefixes illegal characters with the escape character.  Compact, but
+// illegal characters still appear in the string.
+size_t escape(char * buffer, size_t buflen,
+              const char * source, size_t srclen,
+              const char * illegal, char escape);
+// Note: in-place unescaping (buffer == source) is allowed.
+size_t unescape(char * buffer, size_t buflen,
+                const char * source, size_t srclen,
+                char escape);
+
+// Encoding replaces illegal characters with the escape character and 2 hex
+// chars, so it's a little less compact than escape, but completely removes
+// illegal characters.  note that hex digits should not be used as illegal
+// characters.
+size_t encode(char * buffer, size_t buflen,
+              const char * source, size_t srclen,
+              const char * illegal, char escape);
+// Note: in-place decoding (buffer == source) is allowed.
+size_t decode(char * buffer, size_t buflen,
+              const char * source, size_t srclen,
+              char escape);
+
+// Returns a list of characters that may be unsafe for use in the name of a
+// file, suitable for passing to the 'illegal' member of escape or encode.
+const char* unsafe_filename_characters();
+
+// url_encode is an encode operation with a predefined set of illegal characters
+// and escape character (for use in URLs, obviously).
+size_t url_encode(char * buffer, size_t buflen,
+                  const char * source, size_t srclen);
+// Note: in-place decoding (buffer == source) is allowed.
+size_t url_decode(char * buffer, size_t buflen,
+                  const char * source, size_t srclen);
+
+// html_encode prevents data embedded in html from containing markup.
+size_t html_encode(char * buffer, size_t buflen,
+                   const char * source, size_t srclen);
+// Note: in-place decoding (buffer == source) is allowed.
+size_t html_decode(char * buffer, size_t buflen,
+                   const char * source, size_t srclen);
+
+// xml_encode makes data suitable for inside xml attributes and values.
+size_t xml_encode(char * buffer, size_t buflen,
+                  const char * source, size_t srclen);
+// Note: in-place decoding (buffer == source) is allowed.
+size_t xml_decode(char * buffer, size_t buflen,
+                  const char * source, size_t srclen);
+
+// hex_encode shows the hex representation of binary data in ascii.
+size_t hex_encode(char * buffer, size_t buflen,
+                  const char * source, size_t srclen);
+size_t hex_decode(char * buffer, size_t buflen,
+                  const char * source, size_t srclen);
+// helper funtion for hex_encode
+std::string hex_encode(const char * source, size_t srclen);
+
+// Apply any suitable string transform (including the ones above) to an STL
+// string.  Stack-allocated temporary space is used for the transformation,
+// so value and source may refer to the same string.
+typedef size_t (*Transform)(char * buffer, size_t buflen,
+                            const char * source, size_t srclen);
+size_t transform(std::string& value, size_t maxlen, const std::string& source,
+                 Transform t);
+
+// Return the result of applying transform t to source.
+std::string s_transform(const std::string& source, Transform t);
+
+// Convenience wrappers
+inline std::string s_url_encode(const std::string& source) {
+  return s_transform(source, url_encode);
+}
+inline std::string s_url_decode(const std::string& source) {
+  return s_transform(source, url_decode);
+}
+
+// Splits the source string into multiple fields separated by delimiter,
+// with duplicates of delimiter creating empty fields.
+size_t split(const std::string& source, char delimiter,
+             std::vector<std::string>* fields);
+
+// Splits the source string into multiple fields separated by delimiter,
+// with duplicates of delimiter ignored.  Trailing delimiter ignored.
+size_t tokenize(const std::string& source, char delimiter,
+                std::vector<std::string>* fields);
+
+// Safe sprintf to std::string
+//void sprintf(std::string& value, size_t maxlen, const char * format, ...)
+//     PRINTF_FORMAT(3);
+
+// Convert arbitrary values to/from a string.
+
+template <class T>
+static bool ToString(const T &t, std::string* s) {
+  std::ostringstream oss;
+  oss << t;
+  *s = oss.str();
+  return !oss.fail();
+}
+
+template <class T>
+static bool FromString(const std::string& s, T* t) {
+  std::istringstream iss(s);
+  iss >> *t;
+  return !iss.fail();
+}
+
+// Inline versions of the string conversion routines.
+
+template<typename T>
+static inline std::string ToString(T val) {
+  std::string str; ToString(val, &str); return str;
+}
+
+template<typename T>
+static inline T FromString(const std::string& str) {
+  T val; FromString(str, &val); return val;
+}
+
+template<typename T>
+static inline T FromString(const T& defaultValue, const std::string& str) {
+  T val(defaultValue); FromString(str, &val); return val;
+}
+
+// simple function to strip out characters which shouldn't be
+// used in filenames
+char make_char_safe_for_filename(char c);
+
+//////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_STRINGENCODE_H__
diff --git a/talk/base/stringutils.cc b/talk/base/stringutils.cc
new file mode 100644
index 0000000..8d8b7e0
--- /dev/null
+++ b/talk/base/stringutils.cc
@@ -0,0 +1,145 @@
+/*
+ * 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/base/stringutils.h"
+#include "talk/base/common.h"
+
+namespace talk_base {
+
+bool memory_check(const void* memory, int c, size_t count) {
+  const char* char_memory = static_cast<const char*>(memory);
+  char char_c = static_cast<char>(c);
+  for (size_t i = 0; i < count; ++i) {
+    if (char_memory[i] != char_c) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool string_match(const char* target, const char* pattern) {
+  while (*pattern) {
+    if (*pattern == '*') {
+      if (!*++pattern) {
+        return true;
+      }
+      while (*target) {
+        if ((toupper(*pattern) == toupper(*target))
+            && string_match(target + 1, pattern + 1)) {
+          return true;
+        }
+        ++target;
+      }
+      return false;
+    } else {
+      if (toupper(*pattern) != toupper(*target)) {
+        return false;
+      }
+      ++target;
+      ++pattern;
+    }
+  }
+  return !*target;
+}
+
+#ifdef WIN32
+int ascii_string_compare(const wchar_t* s1, const char* s2, size_t n,
+                         CharacterTransformation transformation) {
+  wchar_t c1, c2;
+  while (true) {
+    if (n-- == 0) return 0;
+    c1 = transformation(*s1);
+    // Double check that characters are not UTF-8
+    ASSERT(static_cast<unsigned char>(*s2) < 128);
+    // Note: *s2 gets implicitly promoted to wchar_t
+    c2 = transformation(*s2);
+    if (c1 != c2) return (c1 < c2) ? -1 : 1;
+    if (!c1) return 0;
+    ++s1;
+    ++s2;
+  }
+}
+
+size_t asccpyn(wchar_t* buffer, size_t buflen,
+               const char* source, size_t srclen) {
+  if (buflen <= 0)
+    return 0;
+
+  if (srclen == SIZE_UNKNOWN) {
+    srclen = strlenn(source, buflen - 1);
+  } else if (srclen >= buflen) {
+    srclen = buflen - 1;
+  }
+#if _DEBUG
+  // Double check that characters are not UTF-8
+  for (size_t pos = 0; pos < srclen; ++pos)
+    ASSERT(static_cast<unsigned char>(source[pos]) < 128);
+#endif  // _DEBUG
+  std::copy(source, source + srclen, buffer);
+  buffer[srclen] = 0;
+  return srclen;
+}
+
+#endif  // WIN32
+
+void replace_substrs(const char *search,
+                     size_t search_len,
+                     const char *replace,
+                     size_t replace_len,
+                     std::string *s) {
+  size_t pos = 0;
+  while ((pos = s->find(search, pos, search_len)) != std::string::npos) {
+    s->replace(pos, search_len, replace, replace_len);
+    pos += replace_len;
+  }
+}
+
+bool starts_with(const char *s1, const char *s2) {
+  while (*s2 != '\0') {
+    if (*s1 != *s2) {
+      return false;
+    }
+    s1++;
+    s2++;
+  }
+  return true;
+}
+
+static const std::string kWhitespace(" \n\r\t");
+
+std::string string_trim(const std::string& s) {
+  std::string::size_type first = s.find_first_not_of(kWhitespace);
+  std::string::size_type last  = s.find_last_not_of(kWhitespace);
+
+  if (first == std::string::npos || last == std::string::npos) {
+    return std::string("");
+  }
+
+  return s.substr(first, last - first + 1);
+}
+
+}  // namespace talk_base
diff --git a/talk/base/stringutils.h b/talk/base/stringutils.h
new file mode 100644
index 0000000..6aa9b18
--- /dev/null
+++ b/talk/base/stringutils.h
@@ -0,0 +1,340 @@
+/*
+ * 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_BASE_STRINGUTILS_H__
+#define TALK_BASE_STRINGUTILS_H__
+
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#ifdef WIN32
+#include <malloc.h>
+#include <wchar.h>
+#define alloca _alloca
+#endif  // WIN32
+
+#ifdef POSIX
+#ifdef BSD
+#include <stdlib.h>
+#else  // BSD
+#include <alloca.h>
+#endif  // !BSD
+#endif  // POSIX
+
+#include <cstring>
+#include <string>
+
+#include "talk/base/basictypes.h"
+
+///////////////////////////////////////////////////////////////////////////////
+// Generic string/memory utilities
+///////////////////////////////////////////////////////////////////////////////
+
+#define STACK_ARRAY(TYPE, LEN) static_cast<TYPE*>(::alloca((LEN)*sizeof(TYPE)))
+
+namespace talk_base {
+
+// Complement to memset.  Verifies memory consists of count bytes of value c.
+bool memory_check(const void* memory, int c, size_t count);
+
+// Determines whether the simple wildcard pattern matches target.
+// Alpha characters in pattern match case-insensitively.
+// Asterisks in pattern match 0 or more characters.
+// Ex: string_match("www.TEST.GOOGLE.COM", "www.*.com") -> true
+bool string_match(const char* target, const char* pattern);
+
+}  // namespace talk_base
+
+///////////////////////////////////////////////////////////////////////////////
+// Rename a bunch of common string functions so they are consistent across
+// platforms and between char and wchar_t variants.
+// Here is the full list of functions that are unified:
+//  strlen, strcmp, stricmp, strncmp, strnicmp
+//  strchr, vsnprintf, strtoul, tolowercase
+// tolowercase is like tolower, but not compatible with end-of-file value
+//
+// It's not clear if we will ever use wchar_t strings on unix.  In theory,
+// all strings should be Utf8 all the time, except when interfacing with Win32
+// APIs that require Utf16.
+///////////////////////////////////////////////////////////////////////////////
+
+inline char tolowercase(char c) {
+  return static_cast<char>(tolower(c));
+}
+
+#ifdef WIN32
+
+inline size_t strlen(const wchar_t* s) {
+  return wcslen(s);
+}
+inline int strcmp(const wchar_t* s1, const wchar_t* s2) {
+  return wcscmp(s1, s2);
+}
+inline int stricmp(const wchar_t* s1, const wchar_t* s2) {
+  return _wcsicmp(s1, s2);
+}
+inline int strncmp(const wchar_t* s1, const wchar_t* s2, size_t n) {
+  return wcsncmp(s1, s2, n);
+}
+inline int strnicmp(const wchar_t* s1, const wchar_t* s2, size_t n) {
+  return _wcsnicmp(s1, s2, n);
+}
+inline const wchar_t* strchr(const wchar_t* s, wchar_t c) {
+  return wcschr(s, c);
+}
+inline const wchar_t* strstr(const wchar_t* haystack, const wchar_t* needle) {
+  return wcsstr(haystack, needle);
+}
+#ifndef vsnprintf
+inline int vsnprintf(char* buf, size_t n, const char* fmt, va_list args) {
+  return _vsnprintf(buf, n, fmt, args);
+}
+inline int vsnprintf(wchar_t* buf, size_t n, const wchar_t* fmt, va_list args) {
+  return _vsnwprintf(buf, n, fmt, args);
+}
+#endif // !vsnprintf
+inline unsigned long strtoul(const wchar_t* snum, wchar_t** end, int base) {
+  return wcstoul(snum, end, base);
+}
+inline wchar_t tolowercase(wchar_t c) {
+  return static_cast<wchar_t>(towlower(c));
+}
+
+#endif  // WIN32
+
+#ifdef POSIX
+
+inline int _stricmp(const char* s1, const char* s2) {
+  return strcasecmp(s1, s2);
+}
+inline int _strnicmp(const char* s1, const char* s2, size_t n) {
+  return strncasecmp(s1, s2, n);
+}
+
+#endif // POSIX
+
+///////////////////////////////////////////////////////////////////////////////
+// Traits simplifies porting string functions to be CTYPE-agnostic
+///////////////////////////////////////////////////////////////////////////////
+
+namespace talk_base {
+
+const size_t SIZE_UNKNOWN = static_cast<size_t>(-1);
+
+template<class CTYPE>
+struct Traits {
+  // STL string type
+  //typedef XXX string;
+  // Null-terminated string
+  //inline static const CTYPE* empty_str();
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// String utilities which work with char or wchar_t
+///////////////////////////////////////////////////////////////////////////////
+
+template<class CTYPE>
+inline const CTYPE* nonnull(const CTYPE* str, const CTYPE* def_str = NULL) {
+  return str ? str : (def_str ? def_str : Traits<CTYPE>::empty_str());
+}
+
+template<class CTYPE>
+const CTYPE* strchr(const CTYPE* str, const CTYPE* chs) {
+  for (size_t i=0; str[i]; ++i) {
+    for (size_t j=0; chs[j]; ++j) {
+      if (str[i] == chs[j]) {
+        return str + i;
+      }
+    }
+  }
+  return 0;
+}
+
+template<class CTYPE>
+const CTYPE* strchrn(const CTYPE* str, size_t slen, CTYPE ch) {
+  for (size_t i=0; i<slen && str[i]; ++i) {
+    if (str[i] == ch) {
+      return str + i;
+    }
+  }
+  return 0;
+}
+
+template<class CTYPE>
+size_t strlenn(const CTYPE* buffer, size_t buflen) {
+  size_t bufpos = 0;
+  while (buffer[bufpos] && (bufpos < buflen)) {
+    ++bufpos;
+  }
+  return bufpos;
+}
+
+// Safe versions of strncpy, strncat, snprintf and vsnprintf that always
+// null-terminate.
+
+template<class CTYPE>
+size_t strcpyn(CTYPE* buffer, size_t buflen,
+               const CTYPE* source, size_t srclen = SIZE_UNKNOWN) {
+  if (buflen <= 0)
+    return 0;
+
+  if (srclen == SIZE_UNKNOWN) {
+    srclen = strlenn(source, buflen - 1);
+  } else if (srclen >= buflen) {
+    srclen = buflen - 1;
+  }
+  memcpy(buffer, source, srclen * sizeof(CTYPE));
+  buffer[srclen] = 0;
+  return srclen;
+}
+
+template<class CTYPE>
+size_t strcatn(CTYPE* buffer, size_t buflen,
+               const CTYPE* source, size_t srclen = SIZE_UNKNOWN) {
+  if (buflen <= 0)
+    return 0;
+  
+  size_t bufpos = strlenn(buffer, buflen - 1);
+  return bufpos + strcpyn(buffer + bufpos, buflen - bufpos, source, srclen);
+}
+
+// Some compilers (clang specifically) require vsprintfn be defined before
+// sprintfn.
+template<class CTYPE>
+size_t vsprintfn(CTYPE* buffer, size_t buflen, const CTYPE* format,
+                 va_list args) {
+  int len = vsnprintf(buffer, buflen, format, args);
+  if ((len < 0) || (static_cast<size_t>(len) >= buflen)) {
+    len = static_cast<int>(buflen - 1);
+    buffer[len] = 0;
+  }
+  return len;
+}
+
+template<class CTYPE>
+size_t sprintfn(CTYPE* buffer, size_t buflen, const CTYPE* format, ...);
+/* This works to get GCC to notice printf argument mismatches, but then complains of missing implementation of sprintfn<char>
+template<>
+size_t sprintfn(char* buffer, size_t buflen, const char* format, ...)
+GCC_ATTR(format(printf,3,4));
+*/
+template<class CTYPE>
+size_t sprintfn(CTYPE* buffer, size_t buflen, const CTYPE* format, ...) {
+  va_list args;
+  va_start(args, format);
+  size_t len = vsprintfn(buffer, buflen, format, args);
+  va_end(args);
+  return len;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Allow safe comparing and copying ascii (not UTF-8) with both wide and
+// non-wide character strings.
+///////////////////////////////////////////////////////////////////////////////
+
+inline int asccmp(const char* s1, const char* s2) {
+  return strcmp(s1, s2);
+}
+inline int ascicmp(const char* s1, const char* s2) {
+  return _stricmp(s1, s2);
+}
+inline int ascncmp(const char* s1, const char* s2, size_t n) {
+  return strncmp(s1, s2, n);
+}
+inline int ascnicmp(const char* s1, const char* s2, size_t n) {
+  return _strnicmp(s1, s2, n);
+}
+inline size_t asccpyn(char* buffer, size_t buflen,
+                      const char* source, size_t srclen = SIZE_UNKNOWN) {
+  return strcpyn(buffer, buflen, source, srclen);
+}
+
+#ifdef WIN32
+
+typedef wchar_t(*CharacterTransformation)(wchar_t);
+inline wchar_t identity(wchar_t c) { return c; }
+int ascii_string_compare(const wchar_t* s1, const char* s2, size_t n,
+                         CharacterTransformation transformation);
+
+inline int asccmp(const wchar_t* s1, const char* s2) {
+  return ascii_string_compare(s1, s2, static_cast<size_t>(-1), identity);
+}
+inline int ascicmp(const wchar_t* s1, const char* s2) {
+  return ascii_string_compare(s1, s2, static_cast<size_t>(-1), tolowercase);
+}
+inline int ascncmp(const wchar_t* s1, const char* s2, size_t n) {
+  return ascii_string_compare(s1, s2, n, identity);
+}
+inline int ascnicmp(const wchar_t* s1, const char* s2, size_t n) {
+  return ascii_string_compare(s1, s2, n, tolowercase);
+}
+size_t asccpyn(wchar_t* buffer, size_t buflen,
+               const char* source, size_t srclen = SIZE_UNKNOWN);
+
+#endif  // WIN32
+
+///////////////////////////////////////////////////////////////////////////////
+// Traits<char> specializations
+///////////////////////////////////////////////////////////////////////////////
+
+template<>
+struct Traits<char> {
+  typedef std::string string;
+  inline static const char* empty_str() { return ""; }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Traits<wchar_t> specializations (Windows only, currently)
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef WIN32
+
+template<>
+struct Traits<wchar_t> {
+  typedef std::wstring string;
+  inline static const wchar_t* Traits<wchar_t>::empty_str() { return L""; }
+};
+
+#endif  // WIN32
+
+// Replaces all occurrences of "search" with "replace".
+void replace_substrs(const char *search,
+                     size_t search_len,
+                     const char *replace,
+                     size_t replace_len,
+                     std::string *s);
+
+// True iff s1 starts with s2.
+bool starts_with(const char *s1, const char *s2);
+
+// Remove leading and trailing whitespaces.
+std::string string_trim(const std::string& s);
+
+}  // namespace talk_base
+
+#endif // TALK_BASE_STRINGUTILS_H__
diff --git a/talk/base/task.cc b/talk/base/task.cc
new file mode 100644
index 0000000..ad17438
--- /dev/null
+++ b/talk/base/task.cc
@@ -0,0 +1,296 @@
+/*
+ * 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 "talk/base/task.h"
+#include "talk/base/common.h"
+#include "talk/base/taskrunner.h"
+
+namespace talk_base {
+
+int32 Task::unique_id_seed_ = 0;
+
+Task::Task(TaskParent *parent)
+    : TaskParent(this, parent),
+      state_(STATE_INIT),
+      blocked_(false),
+      done_(false),
+      aborted_(false),
+      busy_(false),
+      error_(false),
+      start_time_(0),
+      timeout_time_(0),
+      timeout_seconds_(0),
+      timeout_suspended_(false)  {
+  unique_id_ = unique_id_seed_++;
+
+  // sanity check that we didn't roll-over our id seed
+  ASSERT(unique_id_ < unique_id_seed_);
+}
+
+Task::~Task() {
+  // Is this task being deleted in the correct manner?
+  ASSERT(!done_ || GetRunner()->is_ok_to_delete(this));
+  ASSERT(state_ == STATE_INIT || done_);
+  ASSERT(state_ == STATE_INIT || blocked_);
+
+  // If the task is being deleted without being done, it
+  // means that it hasn't been removed from its parent.
+  // This happens if a task is deleted outside of TaskRunner.
+  if (!done_) {
+    Stop();
+  }
+}
+
+int64 Task::CurrentTime() {
+  return GetRunner()->CurrentTime();
+}
+
+int64 Task::ElapsedTime() {
+  return CurrentTime() - start_time_;
+}
+
+void Task::Start() {
+  if (state_ != STATE_INIT)
+    return;
+  // Set the start time before starting the task.  Otherwise if the task
+  // finishes quickly and deletes the Task object, setting start_time_
+  // will crash.
+  start_time_ = CurrentTime();
+  GetRunner()->StartTask(this);
+}
+
+void Task::Step() {
+  if (done_) {
+#ifdef _DEBUG
+    // we do not know how !blocked_ happens when done_ - should be impossible.
+    // But it causes problems, so in retail build, we force blocked_, and
+    // under debug we assert.
+    ASSERT(blocked_);
+#else
+    blocked_ = true;
+#endif
+    return;
+  }
+
+  // Async Error() was called
+  if (error_) {
+    done_ = true;
+    state_ = STATE_ERROR;
+    blocked_ = true;
+//   obsolete - an errored task is not considered done now
+//   SignalDone();
+
+    Stop();
+#ifdef _DEBUG
+    // verify that stop removed this from its parent
+    ASSERT(!parent()->IsChildTask(this));
+#endif
+    return;
+  }
+
+  busy_ = true;
+  int new_state = Process(state_);
+  busy_ = false;
+
+  if (aborted_) {
+    Abort(true);  // no need to wake because we're awake
+    return;
+  }
+
+  if (new_state == STATE_BLOCKED) {
+    blocked_ = true;
+    // Let the timeout continue
+  } else {
+    state_ = new_state;
+    blocked_ = false;
+    ResetTimeout();
+  }
+
+  if (new_state == STATE_DONE) {
+    done_ = true;
+  } else if (new_state == STATE_ERROR) {
+    done_ = true;
+    error_ = true;
+  }
+
+  if (done_) {
+//  obsolete - call this yourself
+//    SignalDone();
+
+    Stop();
+#if _DEBUG
+    // verify that stop removed this from its parent
+    ASSERT(!parent()->IsChildTask(this));
+#endif
+    blocked_ = true;
+  }
+}
+
+void Task::Abort(bool nowake) {
+  // Why only check for done_ (instead of "aborted_ || done_")?
+  //
+  // If aborted_ && !done_, it means the logic for aborting still
+  // needs to be executed (because busy_ must have been true when
+  // Abort() was previously called).
+  if (done_)
+    return;
+  aborted_ = true;
+  if (!busy_) {
+    done_ = true;
+    blocked_ = true;
+    error_ = true;
+
+    // "done_" is set before calling "Stop()" to ensure that this code 
+    // doesn't execute more than once (recursively) for the same task.
+    Stop();
+#ifdef _DEBUG
+    // verify that stop removed this from its parent
+    ASSERT(!parent()->IsChildTask(this));
+#endif
+    if (!nowake) {
+      // WakeTasks to self-delete.
+      // Don't call Wake() because it is a no-op after "done_" is set.
+      // Even if Wake() did run, it clears "blocked_" which isn't desireable.
+      GetRunner()->WakeTasks();
+    }
+  }
+}
+
+void Task::Wake() {
+  if (done_)
+    return;
+  if (blocked_) {
+    blocked_ = false;
+    GetRunner()->WakeTasks();
+  }
+}
+
+void Task::Error() {
+  if (error_ || done_)
+    return;
+  error_ = true;
+  Wake();
+}
+
+std::string Task::GetStateName(int state) const {
+  static const std::string STR_BLOCKED("BLOCKED");
+  static const std::string STR_INIT("INIT");
+  static const std::string STR_START("START");
+  static const std::string STR_DONE("DONE");
+  static const std::string STR_ERROR("ERROR");
+  static const std::string STR_RESPONSE("RESPONSE");
+  static const std::string STR_HUH("??");
+  switch (state) {
+    case STATE_BLOCKED: return STR_BLOCKED;
+    case STATE_INIT: return STR_INIT;
+    case STATE_START: return STR_START;
+    case STATE_DONE: return STR_DONE;
+    case STATE_ERROR: return STR_ERROR;
+    case STATE_RESPONSE: return STR_RESPONSE;
+  }
+  return STR_HUH;
+}
+
+int Task::Process(int state) {
+  int newstate = STATE_ERROR;
+
+  if (TimedOut()) {
+    ClearTimeout();
+    newstate = OnTimeout();
+    SignalTimeout();
+  } else {
+    switch (state) {
+      case STATE_INIT:
+        newstate = STATE_START;
+        break;
+      case STATE_START:
+        newstate = ProcessStart();
+        break;
+      case STATE_RESPONSE:
+        newstate = ProcessResponse();
+        break;
+      case STATE_DONE:
+      case STATE_ERROR:
+        newstate = STATE_BLOCKED;
+        break;
+    }
+  }
+
+  return newstate;
+}
+
+void Task::Stop() {
+  // No need to wake because we're either awake or in abort
+  TaskParent::OnStopped(this);
+}
+
+void Task::set_timeout_seconds(const int timeout_seconds) {
+  timeout_seconds_ = timeout_seconds;
+  ResetTimeout();
+}
+
+bool Task::TimedOut() {
+  return timeout_seconds_ &&
+    timeout_time_ &&
+    CurrentTime() >= timeout_time_;
+}
+
+void Task::ResetTimeout() {
+  int64 previous_timeout_time = timeout_time_;
+  bool timeout_allowed = (state_ != STATE_INIT)
+                      && (state_ != STATE_DONE)
+                      && (state_ != STATE_ERROR);
+  if (timeout_seconds_ && timeout_allowed && !timeout_suspended_)
+    timeout_time_ = CurrentTime() +
+                    (timeout_seconds_ * kSecToMsec * kMsecTo100ns);
+  else
+    timeout_time_ = 0;
+
+  GetRunner()->UpdateTaskTimeout(this, previous_timeout_time);
+}
+
+void Task::ClearTimeout() {
+  int64 previous_timeout_time = timeout_time_;
+  timeout_time_ = 0;
+  GetRunner()->UpdateTaskTimeout(this, previous_timeout_time);
+}
+
+void Task::SuspendTimeout() {
+  if (!timeout_suspended_) {
+    timeout_suspended_ = true;
+    ResetTimeout();
+  }
+}
+
+void Task::ResumeTimeout() {
+  if (timeout_suspended_) {
+    timeout_suspended_ = false;
+    ResetTimeout();
+  }
+}
+
+} // namespace talk_base
diff --git a/talk/base/task.h b/talk/base/task.h
new file mode 100644
index 0000000..10e6f5c
--- /dev/null
+++ b/talk/base/task.h
@@ -0,0 +1,194 @@
+/*
+ * 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_BASE_TASK_H__
+#define TALK_BASE_TASK_H__
+
+#include <string>
+#include "talk/base/basictypes.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/taskparent.h"
+
+/////////////////////////////////////////////////////////////////////
+//
+// TASK
+//
+/////////////////////////////////////////////////////////////////////
+//
+// Task is a state machine infrastructure.  States are pushed forward by
+// pushing forwards a TaskRunner that holds on to all Tasks.  The purpose
+// of Task is threefold:
+//
+// (1) It manages ongoing work on the UI thread.  Multitasking without
+// threads, keeping it easy, keeping it real. :-)  It does this by
+// organizing a set of states for each task.  When you return from your
+// Process*() function, you return an integer for the next state.  You do
+// not go onto the next state yourself.  Every time you enter a state,
+// you check to see if you can do anything yet.  If not, you return
+// STATE_BLOCKED.  If you _could_ do anything, do not return
+// STATE_BLOCKED - even if you end up in the same state, return
+// STATE_mysamestate.  When you are done, return STATE_DONE and then the
+// task will self-delete sometime afterwards.
+//
+// (2) It helps you avoid all those reentrancy problems when you chain
+// too many triggers on one thread.  Basically if you want to tell a task
+// to process something for you, you feed your task some information and
+// then you Wake() it.  Don't tell it to process it right away.  If it
+// might be working on something as you send it information, you may want
+// to have a queue in the task.
+//
+// (3) Finally it helps manage parent tasks and children.  If a parent
+// task gets aborted, all the children tasks are too.  The nice thing
+// about this, for example, is if you have one parent task that
+// represents, say, and Xmpp connection, then you can spawn a whole bunch
+// of infinite lifetime child tasks and now worry about cleaning them up.
+//  When the parent task goes to STATE_DONE, the task engine will make
+// sure all those children are aborted and get deleted.
+//
+// Notice that Task has a few built-in states, e.g.,
+//
+// STATE_INIT - the task isn't running yet
+// STATE_START - the task is in its first state
+// STATE_RESPONSE - the task is in its second state
+// STATE_DONE - the task is done
+//
+// STATE_ERROR - indicates an error - we should audit the error code in
+// light of any usage of it to see if it should be improved.  When I
+// first put down the task stuff I didn't have a good sense of what was
+// needed for Abort and Error, and now the subclasses of Task will ground
+// the design in a stronger way.
+//
+// STATE_NEXT - the first undefined state number.  (like WM_USER) - you
+// can start defining more task states there.
+//
+// When you define more task states, just override Process(int state) and
+// add your own switch statement.  If you want to delegate to
+// Task::Process, you can effectively delegate to its switch statement.
+// No fancy method pointers or such - this is all just pretty low tech,
+// easy to debug, and fast.
+//
+// Also notice that Task has some primitive built-in timeout functionality.
+//
+// A timeout is defined as "the task stays in STATE_BLOCKED longer than
+// timeout_seconds_."
+//
+// Descendant classes can override this behavior by calling the
+// various protected methods to change the timeout behavior.  For
+// instance, a descendand might call SuspendTimeout() when it knows
+// that it isn't waiting for anything that might timeout, but isn't
+// yet in the STATE_DONE state.
+//
+
+namespace talk_base {
+
+// Executes a sequence of steps
+class Task : public TaskParent {
+ public:
+  Task(TaskParent *parent);
+  virtual ~Task();
+
+  int32 unique_id() { return unique_id_; }
+
+  void Start();
+  void Step();
+  int GetState() const { return state_; }
+  bool HasError() const { return (GetState() == STATE_ERROR); }
+  bool Blocked() const { return blocked_; }
+  bool IsDone() const { return done_; }
+  int64 ElapsedTime();
+
+  // Called from outside to stop task without any more callbacks
+  void Abort(bool nowake = false);
+
+  bool TimedOut();
+
+  int64 timeout_time() const { return timeout_time_; }
+  int timeout_seconds() const { return timeout_seconds_; }
+  void set_timeout_seconds(int timeout_seconds);
+
+  sigslot::signal0<> SignalTimeout;
+
+  // Called inside the task to signal that the task may be unblocked
+  void Wake();
+
+ protected:
+
+  enum {
+    STATE_BLOCKED = -1,
+    STATE_INIT = 0,
+    STATE_START = 1,
+    STATE_DONE = 2,
+    STATE_ERROR = 3,
+    STATE_RESPONSE = 4,
+    STATE_NEXT = 5,  // Subclasses which need more states start here and higher
+  };
+
+  // Called inside to advise that the task should wake and signal an error
+  void Error();
+
+  int64 CurrentTime();
+
+  virtual std::string GetStateName(int state) const;
+  virtual int Process(int state);
+  virtual void Stop();
+  virtual int ProcessStart() = 0;
+  virtual int ProcessResponse() { return STATE_DONE; }
+
+  void ResetTimeout();
+  void ClearTimeout();
+
+  void SuspendTimeout();
+  void ResumeTimeout();
+
+ protected:
+  virtual int OnTimeout() {
+    // by default, we are finished after timing out
+    return STATE_DONE;
+  }
+
+ private:
+  void Done();
+
+  int state_;
+  bool blocked_;
+  bool done_;
+  bool aborted_;
+  bool busy_;
+  bool error_;
+  int64 start_time_;
+  int64 timeout_time_;
+  int timeout_seconds_;
+  bool timeout_suspended_;
+  int32 unique_id_;
+  
+  static int32 unique_id_seed_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_TASK_H__
diff --git a/talk/base/taskparent.cc b/talk/base/taskparent.cc
new file mode 100644
index 0000000..f05ee82
--- /dev/null
+++ b/talk/base/taskparent.cc
@@ -0,0 +1,112 @@
+/*
+ * 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 <algorithm>
+
+#include "talk/base/taskparent.h"
+
+#include "talk/base/task.h"
+#include "talk/base/taskrunner.h"
+
+namespace talk_base {
+
+TaskParent::TaskParent(Task* derived_instance, TaskParent *parent)
+    : parent_(parent) {
+  ASSERT(derived_instance != NULL);
+  ASSERT(parent != NULL);
+  runner_ = parent->GetRunner();
+  parent_->AddChild(derived_instance);
+  Initialize();
+}
+
+TaskParent::TaskParent(TaskRunner *derived_instance)
+    : parent_(NULL),
+      runner_(derived_instance) {
+  ASSERT(derived_instance != NULL);
+  Initialize();
+}
+
+// Does common initialization of member variables
+void TaskParent::Initialize() {
+  children_.reset(new ChildSet());
+  child_error_ = false;
+}
+
+void TaskParent::AddChild(Task *child) {
+  children_->insert(child);
+}
+
+#ifdef _DEBUG
+bool TaskParent::IsChildTask(Task *task) {
+  ASSERT(task != NULL);
+  return task->parent_ == this && children_->find(task) != children_->end();
+}
+#endif
+
+bool TaskParent::AllChildrenDone() {
+  for (ChildSet::iterator it = children_->begin();
+       it != children_->end();
+       ++it) {
+    if (!(*it)->IsDone())
+      return false;
+  }
+  return true;
+}
+
+bool TaskParent::AnyChildError() {
+  return child_error_;
+}
+
+void TaskParent::AbortAllChildren() {
+  if (children_->size() > 0) {
+#ifdef _DEBUG
+    runner_->IncrementAbortCount();
+#endif
+
+    ChildSet copy = *children_;
+    for (ChildSet::iterator it = copy.begin(); it != copy.end(); ++it) {
+      (*it)->Abort(true);  // Note we do not wake
+    }
+
+#ifdef _DEBUG
+    runner_->DecrementAbortCount();
+#endif
+  }
+}
+
+void TaskParent::OnStopped(Task *task) {
+  AbortAllChildren();
+  parent_->OnChildStopped(task);
+}
+
+void TaskParent::OnChildStopped(Task *child) {
+  if (child->HasError())
+    child_error_ = true;
+  children_->erase(child);
+}
+
+} // namespace talk_base
diff --git a/talk/base/taskparent.h b/talk/base/taskparent.h
new file mode 100644
index 0000000..a6c5795
--- /dev/null
+++ b/talk/base/taskparent.h
@@ -0,0 +1,89 @@
+/*
+ * 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_BASE_TASKPARENT_H__
+#define TALK_BASE_TASKPARENT_H__
+
+#include <set>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/scoped_ptr.h"
+
+namespace talk_base {
+
+class Task;
+class TaskRunner;
+
+class TaskParent {
+ public:
+  TaskParent(Task *derived_instance, TaskParent *parent);
+  explicit TaskParent(TaskRunner *derived_instance);
+  virtual ~TaskParent() { }
+
+  TaskParent *GetParent() { return parent_; }
+  TaskRunner *GetRunner() { return runner_; }
+
+  // Retrieves a parent that corresponds to the given "code".  The code
+  // should be defined in a unique manner for the given subtree.  This
+  // method will crash (when the parent_ is NULL) if there is no corresponding
+  // parent.
+  // 
+  // Example use:
+  //  XmppClient* client =
+  //      static_cast<XmppClient*>(parent->GetParent(XMPP_CLIENT_TASK_CODE));
+  virtual TaskParent *GetParent(int code) { return parent_->GetParent(code); }
+
+  bool AllChildrenDone();
+  bool AnyChildError();
+#ifdef _DEBUG
+  bool IsChildTask(Task *task);
+#endif
+
+ protected:
+  void OnStopped(Task *task);
+  void AbortAllChildren();
+  TaskParent *parent() {
+    return parent_;
+  }
+
+ private:
+  void Initialize();
+  void OnChildStopped(Task *child);
+  void AddChild(Task *child);
+
+  TaskParent *parent_;
+  TaskRunner *runner_;
+  bool child_error_;
+  typedef std::set<Task *> ChildSet;
+  scoped_ptr<ChildSet> children_;
+  DISALLOW_EVIL_CONSTRUCTORS(TaskParent);
+};
+
+
+} // namespace talk_base
+
+#endif  // TALK_BASE_TASKPARENT_H__
diff --git a/talk/base/taskrunner.cc b/talk/base/taskrunner.cc
new file mode 100644
index 0000000..0c0816c
--- /dev/null
+++ b/talk/base/taskrunner.cc
@@ -0,0 +1,241 @@
+/*
+ * 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 <algorithm>
+
+#include "talk/base/taskrunner.h"
+
+#include "talk/base/common.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/task.h"
+#include "talk/base/logging.h"
+
+namespace talk_base {
+
+TaskRunner::TaskRunner()
+  : TaskParent(this),
+    next_timeout_task_(NULL),
+    tasks_running_(false)
+#ifdef _DEBUG
+    , abort_count_(0),
+    deleting_task_(NULL)
+#endif
+{
+}
+
+TaskRunner::~TaskRunner() {
+  // this kills and deletes children silently!
+  AbortAllChildren();
+  InternalRunTasks(true);
+}
+
+void TaskRunner::StartTask(Task * task) {
+  tasks_.push_back(task);
+
+  // the task we just started could be about to timeout --
+  // make sure our "next timeout task" is correct
+  UpdateTaskTimeout(task, 0);
+
+  WakeTasks();
+}
+
+void TaskRunner::RunTasks() {
+  InternalRunTasks(false);
+}
+
+void TaskRunner::InternalRunTasks(bool in_destructor) {
+  // This shouldn't run while an abort is happening.
+  // If that occurs, then tasks may be deleted in this method,
+  // but pointers to them will still be in the
+  // "ChildSet copy" in TaskParent::AbortAllChildren.
+  // Subsequent use of those task may cause data corruption or crashes.  
+  ASSERT(!abort_count_);
+  // Running continues until all tasks are Blocked (ok for a small # of tasks)
+  if (tasks_running_) {
+    return;  // don't reenter
+  }
+
+  tasks_running_ = true;
+
+  int64 previous_timeout_time = next_task_timeout();
+
+  int did_run = true;
+  while (did_run) {
+    did_run = false;
+    // use indexing instead of iterators because tasks_ may grow
+    for (size_t i = 0; i < tasks_.size(); ++i) {
+      while (!tasks_[i]->Blocked()) {
+        tasks_[i]->Step();
+        did_run = true;
+      }
+    }
+  }
+  // Tasks are deleted when running has paused
+  bool need_timeout_recalc = false;
+  for (size_t i = 0; i < tasks_.size(); ++i) {
+    if (tasks_[i]->IsDone()) {
+      Task* task = tasks_[i];
+      if (next_timeout_task_ &&
+          task->unique_id() == next_timeout_task_->unique_id()) {
+        next_timeout_task_ = NULL;
+        need_timeout_recalc = true;
+      }
+
+#ifdef _DEBUG
+      deleting_task_ = task;
+#endif
+      delete task;
+#ifdef _DEBUG
+      deleting_task_ = NULL;
+#endif
+      tasks_[i] = NULL;
+    }
+  }
+  // Finally, remove nulls
+  std::vector<Task *>::iterator it;
+  it = std::remove(tasks_.begin(),
+                   tasks_.end(),
+                   reinterpret_cast<Task *>(NULL));
+
+  tasks_.erase(it, tasks_.end());
+
+  if (need_timeout_recalc)
+    RecalcNextTimeout(NULL);
+
+  // Make sure that adjustments are done to account
+  // for any timeout changes (but don't call this
+  // while being destroyed since it calls a pure virtual function).
+  if (!in_destructor)
+    CheckForTimeoutChange(previous_timeout_time);
+
+  tasks_running_ = false;
+}
+
+void TaskRunner::PollTasks() {
+  // see if our "next potentially timed-out task" has indeed timed out.
+  // If it has, wake it up, then queue up the next task in line
+  // Repeat while we have new timed-out tasks.
+  // TODO: We need to guard against WakeTasks not updating
+  // next_timeout_task_. Maybe also add documentation in the header file once
+  // we understand this code better.
+  Task* old_timeout_task = NULL;
+  while (next_timeout_task_ &&
+      old_timeout_task != next_timeout_task_ &&
+      next_timeout_task_->TimedOut()) {
+    old_timeout_task = next_timeout_task_;
+    next_timeout_task_->Wake();
+    WakeTasks();
+  }
+}
+
+int64 TaskRunner::next_task_timeout() const {
+  if (next_timeout_task_) {
+    return next_timeout_task_->timeout_time();
+  }
+  return 0;
+}
+
+// this function gets called frequently -- when each task changes
+// state to something other than DONE, ERROR or BLOCKED, it calls
+// ResetTimeout(), which will call this function to make sure that
+// the next timeout-able task hasn't changed.  The logic in this function
+// prevents RecalcNextTimeout() from getting called in most cases,
+// effectively making the task scheduler O-1 instead of O-N
+
+void TaskRunner::UpdateTaskTimeout(Task* task,
+                                   int64 previous_task_timeout_time) {
+  ASSERT(task != NULL);
+  int64 previous_timeout_time = next_task_timeout();
+  bool task_is_timeout_task = next_timeout_task_ != NULL &&
+      task->unique_id() == next_timeout_task_->unique_id();
+  if (task_is_timeout_task) {
+    previous_timeout_time = previous_task_timeout_time;
+  }
+
+  // if the relevant task has a timeout, then
+  // check to see if it's closer than the current
+  // "about to timeout" task
+  if (task->timeout_time()) {
+    if (next_timeout_task_ == NULL ||
+        (task->timeout_time() <= next_timeout_task_->timeout_time())) {
+      next_timeout_task_ = task;
+    }
+  } else if (task_is_timeout_task) {
+    // otherwise, if the task doesn't have a timeout,
+    // and it used to be our "about to timeout" task,
+    // walk through all the tasks looking for the real
+    // "about to timeout" task
+    RecalcNextTimeout(task);
+  }
+
+  // Note when task_running_, then the running routine
+  // (TaskRunner::InternalRunTasks) is responsible for calling
+  // CheckForTimeoutChange.
+  if (!tasks_running_) {
+    CheckForTimeoutChange(previous_timeout_time);
+  }
+}
+
+void TaskRunner::RecalcNextTimeout(Task *exclude_task) {
+  // walk through all the tasks looking for the one
+  // which satisfies the following:
+  //   it's not finished already
+  //   we're not excluding it
+  //   it has the closest timeout time
+
+  int64 next_timeout_time = 0;
+  next_timeout_task_ = NULL;
+
+  for (size_t i = 0; i < tasks_.size(); ++i) {
+    Task *task = tasks_[i];
+    // if the task isn't complete, and it actually has a timeout time
+    if (!task->IsDone() && (task->timeout_time() > 0))
+      // if it doesn't match our "exclude" task
+      if (exclude_task == NULL ||
+          exclude_task->unique_id() != task->unique_id())
+        // if its timeout time is sooner than our current timeout time
+        if (next_timeout_time == 0 ||
+            task->timeout_time() <= next_timeout_time) {
+          // set this task as our next-to-timeout
+          next_timeout_time = task->timeout_time();
+          next_timeout_task_ = task;
+        }
+  }
+}
+
+void TaskRunner::CheckForTimeoutChange(int64 previous_timeout_time) {
+  int64 next_timeout = next_task_timeout();
+  bool timeout_change = (previous_timeout_time == 0 && next_timeout != 0) ||
+      next_timeout < previous_timeout_time ||
+      (previous_timeout_time <= CurrentTime() &&
+       previous_timeout_time != next_timeout);
+  if (timeout_change) {
+    OnTimeoutChange();
+  }
+}
+
+} // namespace talk_base
diff --git a/talk/base/taskrunner.h b/talk/base/taskrunner.h
new file mode 100644
index 0000000..f34a609
--- /dev/null
+++ b/talk/base/taskrunner.h
@@ -0,0 +1,117 @@
+/*
+ * 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_BASE_TASKRUNNER_H__
+#define TALK_BASE_TASKRUNNER_H__
+
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/taskparent.h"
+
+namespace talk_base {
+class Task;
+
+const int64 kSecToMsec = 1000;
+const int64 kMsecTo100ns = 10000;
+const int64 kSecTo100ns = kSecToMsec * kMsecTo100ns;
+
+class TaskRunner : public TaskParent, public sigslot::has_slots<> {
+ public:
+  TaskRunner();
+  virtual ~TaskRunner();
+
+  virtual void WakeTasks() = 0;
+
+  // Returns the current time in 100ns units.  It is used for
+  // determining timeouts.  The origin is not important, only
+  // the units and that rollover while the computer is running.
+  //
+  // On Windows, GetSystemTimeAsFileTime is the typical implementation.
+  virtual int64 CurrentTime() = 0 ;
+
+  void StartTask(Task *task);
+  void RunTasks();
+  void PollTasks();
+
+  void UpdateTaskTimeout(Task *task, int64 previous_task_timeout_time);
+
+#ifdef _DEBUG
+  bool is_ok_to_delete(Task* task) {
+    return task == deleting_task_;
+  }
+
+  void IncrementAbortCount() {
+    ++abort_count_;
+  }
+
+  void DecrementAbortCount() {
+    --abort_count_;
+  }
+#endif
+
+  // Returns the next absolute time when a task times out
+  // OR "0" if there is no next timeout.
+  int64 next_task_timeout() const;
+
+ protected:
+  // The primary usage of this method is to know if
+  // a callback timer needs to be set-up or adjusted.
+  // This method will be called
+  //  * when the next_task_timeout() becomes a smaller value OR
+  //  * when next_task_timeout() has changed values and the previous
+  //    value is in the past.
+  //
+  // If the next_task_timeout moves to the future, this method will *not*
+  // get called (because it subclass should check next_task_timeout()
+  // when its timer goes off up to see if it needs to set-up a new timer).
+  //
+  // Note that this maybe called conservatively.  In that it may be
+  // called when no time change has happened.
+  virtual void OnTimeoutChange() {
+    // by default, do nothing.
+  }
+
+ private:
+  void InternalRunTasks(bool in_destructor);
+  void CheckForTimeoutChange(int64 previous_timeout_time);
+
+  std::vector<Task *> tasks_;
+  Task *next_timeout_task_;
+  bool tasks_running_;
+#ifdef _DEBUG
+  int abort_count_;
+  Task* deleting_task_;
+#endif
+
+  void RecalcNextTimeout(Task *exclude_task);
+};
+
+} // namespace talk_base
+
+#endif  // TASK_BASE_TASKRUNNER_H__
diff --git a/talk/base/thread.cc b/talk/base/thread.cc
new file mode 100644
index 0000000..28f3e48
--- /dev/null
+++ b/talk/base/thread.cc
@@ -0,0 +1,527 @@
+/*
+ * 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/base/thread.h"
+
+#if defined(WIN32)
+#include <comdef.h>
+#elif defined(POSIX)
+#include <time.h>
+#endif
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/time.h"
+
+#ifdef OSX_USE_COCOA
+#ifndef OSX
+#error OSX_USE_COCOA is defined but not OSX
+#endif
+#include "talk/base/maccocoathreadhelper.h"
+#include "talk/base/scoped_autorelease_pool.h"
+#endif
+
+namespace talk_base {
+
+ThreadManager g_thmgr;
+
+#ifdef POSIX
+pthread_key_t ThreadManager::key_;
+
+ThreadManager::ThreadManager() {
+  pthread_key_create(&key_, NULL);
+  main_thread_ = WrapCurrentThread();
+#if defined(OSX_USE_COCOA)
+  InitCocoaMultiThreading();
+#endif
+}
+
+ThreadManager::~ThreadManager() {
+#ifdef OSX_USE_COCOA
+  // This is called during exit, at which point apparently no NSAutoreleasePools
+  // are available; but we might still need them to do cleanup (or we get the
+  // "no autoreleasepool in place, just leaking" warning when exiting).
+  ScopedAutoreleasePool pool;
+#endif
+  UnwrapCurrentThread();
+  // Unwrap deletes main_thread_ automatically.
+  pthread_key_delete(key_);
+}
+
+Thread *ThreadManager::CurrentThread() {
+  return static_cast<Thread *>(pthread_getspecific(key_));
+}
+
+void ThreadManager::SetCurrent(Thread *thread) {
+  pthread_setspecific(key_, thread);
+}
+#endif
+
+#ifdef WIN32
+DWORD ThreadManager::key_;
+
+ThreadManager::ThreadManager() {
+  key_ = TlsAlloc();
+  main_thread_ = WrapCurrentThread();
+}
+
+ThreadManager::~ThreadManager() {
+  UnwrapCurrentThread();
+  TlsFree(key_);
+}
+
+Thread *ThreadManager::CurrentThread() {
+  return static_cast<Thread *>(TlsGetValue(key_));
+}
+
+void ThreadManager::SetCurrent(Thread *thread) {
+  TlsSetValue(key_, thread);
+}
+#endif
+
+// static
+Thread *ThreadManager::WrapCurrentThread() {
+  Thread* result = CurrentThread();
+  if (NULL == result) {
+    result = new Thread();
+#if defined(WIN32)
+    // We explicitly ask for no rights other than synchronization.
+    // This gives us the best chance of succeeding.
+    result->thread_ = OpenThread(SYNCHRONIZE, FALSE, GetCurrentThreadId());
+    if (!result->thread_)
+      LOG_GLE(LS_ERROR) << "Unable to get handle to thread.";
+#elif defined(POSIX)
+    result->thread_ = pthread_self();
+#endif
+    result->owned_ = false;
+    result->started_ = true;
+    SetCurrent(result);
+  }
+
+  return result;
+}
+
+// static
+void ThreadManager::UnwrapCurrentThread() {
+  Thread* t = CurrentThread();
+  if (t && !(t->IsOwned())) {
+    // Clears the platform-specific thread-specific storage.
+    SetCurrent(NULL);
+#ifdef WIN32
+    if (!CloseHandle(t->thread_)) {
+      LOG_GLE(LS_ERROR) << "When unwrapping thread, failed to close handle.";
+    }
+#endif
+    t->started_ = false;
+    delete t;
+  }
+}
+
+void ThreadManager::Add(Thread *thread) {
+  CritScope cs(&crit_);
+  threads_.push_back(thread);
+}
+
+void ThreadManager::Remove(Thread *thread) {
+  CritScope cs(&crit_);
+  threads_.erase(std::remove(threads_.begin(), threads_.end(), thread),
+                 threads_.end());
+}
+
+void ThreadManager::StopAllThreads_() {
+  // TODO: In order to properly implement, Threads need to be ref-counted.
+  CritScope cs(&g_thmgr.crit_);
+  for (size_t i = 0; i < g_thmgr.threads_.size(); ++i) {
+    g_thmgr.threads_[i]->Stop();
+  }
+}
+
+struct ThreadInit {
+  Thread* thread;
+  Runnable* runnable;
+};
+
+Thread::Thread(SocketServer* ss)
+    : MessageQueue(ss),
+      priority_(PRIORITY_NORMAL),
+      started_(false),
+      has_sends_(false),
+#if defined(WIN32)
+      thread_(NULL),
+#endif
+      owned_(true) {
+  g_thmgr.Add(this);
+  SetName("Thread", this);  // default name
+}
+
+Thread::~Thread() {
+  Stop();
+  if (active_)
+    Clear(NULL);
+  g_thmgr.Remove(this);
+}
+
+bool Thread::SleepMs(int milliseconds) {
+#ifdef WIN32
+  ::Sleep(milliseconds);
+  return true;
+#else
+  // POSIX has both a usleep() and a nanosleep(), but the former is deprecated,
+  // so we use nanosleep() even though it has greater precision than necessary.
+  struct timespec ts;
+  ts.tv_sec = milliseconds / 1000;
+  ts.tv_nsec = (milliseconds % 1000) * 1000000;
+  int ret = nanosleep(&ts, NULL);
+  if (ret != 0) {
+    LOG_ERR(LS_WARNING) << "nanosleep() returning early";
+    return false;
+  }
+  return true;
+#endif
+}
+
+bool Thread::SetName(const std::string& name, const void* obj) {
+  if (started_) return false;
+  name_ = name;
+  if (obj) {
+    char buf[16];
+    sprintfn(buf, sizeof(buf), " 0x%p", obj);
+    name_ += buf;
+  }
+  return true;
+}
+
+bool Thread::SetPriority(ThreadPriority priority) {
+  if (started_) return false;
+  priority_ = priority;
+  return true;
+}
+
+bool Thread::Start(Runnable* runnable) {
+  ASSERT(owned_);
+  if (!owned_) return false;
+  ASSERT(!started_);
+  if (started_) return false;
+
+  ThreadInit* init = new ThreadInit;
+  init->thread = this;
+  init->runnable = runnable;
+#if defined(WIN32)
+  DWORD flags = 0;
+  if (priority_ != PRIORITY_NORMAL) {
+    flags = CREATE_SUSPENDED;
+  }
+  thread_ = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)PreRun, init, flags,
+                         NULL);
+  if (thread_) {
+    if (priority_ != PRIORITY_NORMAL) {
+      if (priority_ == PRIORITY_HIGH) {
+        ::SetThreadPriority(thread_, THREAD_PRIORITY_HIGHEST);
+      } else if (priority_ == PRIORITY_ABOVE_NORMAL) {
+        ::SetThreadPriority(thread_, THREAD_PRIORITY_ABOVE_NORMAL);
+      } else if (priority_ == PRIORITY_IDLE) {
+        ::SetThreadPriority(thread_, THREAD_PRIORITY_IDLE);
+      }
+      ::ResumeThread(thread_);
+    }
+  } else {
+    return false;
+  }
+#elif defined(POSIX)
+  pthread_attr_t attr;
+  pthread_attr_init(&attr);
+  if (priority_ != PRIORITY_NORMAL) {
+    if (priority_ == PRIORITY_IDLE) {
+      // There is no POSIX-standard way to set a below-normal priority for an
+      // individual thread (only whole process), so let's not support it.
+      LOG(LS_WARNING) << "PRIORITY_IDLE not supported";
+    } else {
+      // Set real-time round-robin policy.
+      if (pthread_attr_setschedpolicy(&attr, SCHED_RR) != 0) {
+        LOG(LS_ERROR) << "pthread_attr_setschedpolicy";
+      }
+      struct sched_param param;
+      if (pthread_attr_getschedparam(&attr, &param) != 0) {
+        LOG(LS_ERROR) << "pthread_attr_getschedparam";
+      } else {
+        // The numbers here are arbitrary.
+        if (priority_ == PRIORITY_HIGH) {
+          param.sched_priority = 6;           // 6 = HIGH
+        } else {
+          ASSERT(priority_ == PRIORITY_ABOVE_NORMAL);
+          param.sched_priority = 4;           // 4 = ABOVE_NORMAL
+        }
+        if (pthread_attr_setschedparam(&attr, &param) != 0) {
+          LOG(LS_ERROR) << "pthread_attr_setschedparam";
+        }
+      }
+    }
+  }
+  int error_code = pthread_create(&thread_, &attr, PreRun, init);
+  if (0 != error_code) {
+    LOG(LS_ERROR) << "Unable to create pthread, error " << error_code;
+    return false;
+  }
+#endif
+  started_ = true;
+  return true;
+}
+
+void Thread::Join() {
+  if (started_) {
+    ASSERT(!IsCurrent());
+#if defined(WIN32)
+    WaitForSingleObject(thread_, INFINITE);
+    CloseHandle(thread_);
+    thread_ = NULL;
+#elif defined(POSIX)
+    void *pv;
+    pthread_join(thread_, &pv);
+#endif
+    started_ = false;
+  }
+}
+
+#ifdef WIN32
+// As seen on MSDN.
+// http://msdn.microsoft.com/en-us/library/xcb2z8hs(VS.71).aspx
+#define MSDEV_SET_THREAD_NAME  0x406D1388
+typedef struct tagTHREADNAME_INFO {
+  DWORD dwType;
+  LPCSTR szName;
+  DWORD dwThreadID;
+  DWORD dwFlags;
+} THREADNAME_INFO;
+
+void SetThreadName(DWORD dwThreadID, LPCSTR szThreadName) {
+  THREADNAME_INFO info;
+  info.dwType = 0x1000;
+  info.szName = szThreadName;
+  info.dwThreadID = dwThreadID;
+  info.dwFlags = 0;
+
+  __try {
+    RaiseException(MSDEV_SET_THREAD_NAME, 0, sizeof(info) / sizeof(DWORD),
+                   reinterpret_cast<DWORD*>(&info));
+  }
+  __except(EXCEPTION_CONTINUE_EXECUTION) {
+  }
+}
+#endif  // WIN32
+
+void* Thread::PreRun(void* pv) {
+  ThreadInit* init = static_cast<ThreadInit*>(pv);
+  ThreadManager::SetCurrent(init->thread);
+#if defined(WIN32)
+  SetThreadName(GetCurrentThreadId(), init->thread->name_.c_str());
+#elif defined(POSIX)
+  // TODO: See if naming exists for pthreads.
+#endif
+#ifdef OSX_USE_COCOA
+  // Make sure the new thread has an autoreleasepool
+  ScopedAutoreleasePool pool;
+#endif
+  if (init->runnable) {
+    init->runnable->Run(init->thread);
+  } else {
+    init->thread->Run();
+  }
+  delete init;
+  return NULL;
+}
+
+void Thread::Run() {
+  ProcessMessages(kForever);
+}
+
+bool Thread::IsOwned() {
+  return owned_;
+}
+
+void Thread::Stop() {
+  MessageQueue::Quit();
+  Join();
+}
+
+void Thread::Send(MessageHandler *phandler, uint32 id, MessageData *pdata) {
+  if (fStop_)
+    return;
+
+  // Sent messages are sent to the MessageHandler directly, in the context
+  // of "thread", like Win32 SendMessage. If in the right context,
+  // call the handler directly.
+
+  Message msg;
+  msg.phandler = phandler;
+  msg.message_id = id;
+  msg.pdata = pdata;
+  if (IsCurrent()) {
+    phandler->OnMessage(&msg);
+    return;
+  }
+
+  AutoThread thread;
+  Thread *current_thread = Thread::Current();
+  ASSERT(current_thread != NULL);  // AutoThread ensures this
+
+  bool ready = false;
+  {
+    CritScope cs(&crit_);
+    EnsureActive();
+    _SendMessage smsg;
+    smsg.thread = current_thread;
+    smsg.msg = msg;
+    smsg.ready = &ready;
+    sendlist_.push_back(smsg);
+    has_sends_ = true;
+  }
+
+  // Wait for a reply
+
+  ss_->WakeUp();
+
+  bool waited = false;
+  while (!ready) {
+    current_thread->ReceiveSends();
+    current_thread->socketserver()->Wait(kForever, false);
+    waited = true;
+  }
+
+  // Our Wait loop above may have consumed some WakeUp events for this
+  // MessageQueue, that weren't relevant to this Send.  Losing these WakeUps can
+  // cause problems for some SocketServers.
+  //
+  // Concrete example:
+  // Win32SocketServer on thread A calls Send on thread B.  While processing the
+  // message, thread B Posts a message to A.  We consume the wakeup for that
+  // Post while waiting for the Send to complete, which means that when we exit
+  // this loop, we need to issue another WakeUp, or else the Posted message
+  // won't be processed in a timely manner.
+
+  if (waited) {
+    current_thread->socketserver()->WakeUp();
+  }
+}
+
+void Thread::ReceiveSends() {
+  // Before entering critical section, check boolean.
+
+  if (!has_sends_)
+    return;
+
+  // Receive a sent message. Cleanup scenarios:
+  // - thread sending exits: We don't allow this, since thread can exit
+  //   only via Join, so Send must complete.
+  // - thread receiving exits: Wakeup/set ready in Thread::Clear()
+  // - object target cleared: Wakeup/set ready in Thread::Clear()
+  crit_.Enter();
+  while (!sendlist_.empty()) {
+    _SendMessage smsg = sendlist_.front();
+    sendlist_.pop_front();
+    crit_.Leave();
+    smsg.msg.phandler->OnMessage(&smsg.msg);
+    crit_.Enter();
+    *smsg.ready = true;
+    smsg.thread->socketserver()->WakeUp();
+  }
+  has_sends_ = false;
+  crit_.Leave();
+}
+
+void Thread::Clear(MessageHandler *phandler, uint32 id,
+                   MessageList* removed) {
+  CritScope cs(&crit_);
+
+  // Remove messages on sendlist_ with phandler
+  // Object target cleared: remove from send list, wakeup/set ready
+  // if sender not NULL.
+
+  std::list<_SendMessage>::iterator iter = sendlist_.begin();
+  while (iter != sendlist_.end()) {
+    _SendMessage smsg = *iter;
+    if (smsg.msg.Match(phandler, id)) {
+      if (removed) {
+        removed->push_back(smsg.msg);
+      } else {
+        delete smsg.msg.pdata;
+      }
+      iter = sendlist_.erase(iter);
+      *smsg.ready = true;
+      smsg.thread->socketserver()->WakeUp();
+      continue;
+    }
+    ++iter;
+  }
+
+  MessageQueue::Clear(phandler, id, removed);
+}
+
+bool Thread::ProcessMessages(int cmsLoop) {
+  uint32 msEnd = (kForever == cmsLoop) ? 0 : TimeAfter(cmsLoop);
+  int cmsNext = cmsLoop;
+
+  while (true) {
+    Message msg;
+    if (!Get(&msg, cmsNext))
+      return !IsQuitting();
+    Dispatch(&msg);
+
+    if (cmsLoop != kForever) {
+      cmsNext = TimeUntil(msEnd);
+      if (cmsNext < 0)
+        return true;
+    }
+  }
+}
+
+AutoThread::AutoThread(SocketServer* ss) : Thread(ss) {
+  if (!ThreadManager::CurrentThread()) {
+    ThreadManager::SetCurrent(this);
+  }
+}
+
+AutoThread::~AutoThread() {
+  if (ThreadManager::CurrentThread() == this) {
+    ThreadManager::SetCurrent(NULL);
+  }
+}
+
+#ifdef WIN32
+void ComThread::Run() {
+  HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
+  ASSERT(SUCCEEDED(hr));
+  if (SUCCEEDED(hr)) {
+    Thread::Run();
+    CoUninitialize();
+  } else {
+    LOG(LS_ERROR) << "CoInitialize failed, hr=" << hr;
+  }
+}
+#endif
+
+}  // namespace talk_base
diff --git a/talk/base/thread.h b/talk/base/thread.h
new file mode 100644
index 0000000..36b9e76
--- /dev/null
+++ b/talk/base/thread.h
@@ -0,0 +1,245 @@
+/*
+ * 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_BASE_THREAD_H_
+#define TALK_BASE_THREAD_H_
+
+#include <algorithm>
+#include <list>
+#include <string>
+#include <vector>
+
+#ifdef POSIX
+#include <pthread.h>
+#endif
+
+#include "talk/base/messagequeue.h"
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#endif
+
+namespace talk_base {
+
+class Thread;
+
+class ThreadManager {
+public:
+  ThreadManager();
+  ~ThreadManager();
+
+  static Thread *CurrentThread();
+  static void SetCurrent(Thread *thread);
+  void Add(Thread *thread);
+  void Remove(Thread *thread);
+
+  // Returns a thread object with its thread_ ivar set
+  // to whatever the OS uses to represent the thread.
+  // If there already *is* a Thread object corresponding to this thread,
+  // this method will return that.  Otherwise it creates a new Thread
+  // object whose wrapped() method will return true, and whose
+  // handle will, on Win32, be opened with only synchronization privileges -
+  // if you need more privilegs, rather than changing this method, please
+  // write additional code to adjust the privileges, or call a different
+  // factory method of your own devising, because this one gets used in
+  // unexpected contexts (like inside browser plugins) and it would be a
+  // shame to break it.  It is also conceivable on Win32 that we won't even
+  // be able to get synchronization privileges, in which case the result
+  // will have a NULL handle.
+  static Thread *WrapCurrentThread();
+  static void UnwrapCurrentThread();
+
+  static void StopAllThreads_();  // Experimental
+
+private:
+  Thread *main_thread_;
+  std::vector<Thread *> threads_;
+  CriticalSection crit_;
+
+#ifdef POSIX
+  static pthread_key_t key_;
+#endif
+
+#ifdef WIN32
+  static DWORD key_;
+#endif
+};
+
+class Thread;
+
+struct _SendMessage {
+  _SendMessage() {}
+  Thread *thread;
+  Message msg;
+  bool *ready;
+};
+
+enum ThreadPriority {
+  PRIORITY_HIGH,
+  PRIORITY_ABOVE_NORMAL,
+  PRIORITY_NORMAL,
+  PRIORITY_IDLE,
+};
+
+class Runnable {
+ public:
+  virtual ~Runnable() {}
+  virtual void Run(Thread* thread) = 0;
+};
+
+class Thread : public MessageQueue {
+public:
+  Thread(SocketServer* ss = NULL);
+  virtual ~Thread();
+
+  static inline Thread* Current() {
+    return ThreadManager::CurrentThread();
+  }
+
+  bool IsCurrent() const {
+    return (ThreadManager::CurrentThread() == this);
+  }
+
+  // Sleeps the calling thread for the specified number of milliseconds, during
+  // which time no processing is performed. Returns false if sleeping was
+  // interrupted by a signal (POSIX only).
+  static bool SleepMs(int millis);
+
+  // Sets the thread's name, for debugging. Must be called before Start().
+  // If |obj| is non-NULL, its value is appended to |name|.
+  const std::string& name() const { return name_; }
+  bool SetName(const std::string& name, const void* obj);
+
+  // Sets the thread's priority. Must be called before Start().
+  ThreadPriority priority() const { return priority_; }
+  bool SetPriority(ThreadPriority priority);
+
+  // Starts the execution of the thread.
+  bool started() const { return started_; }
+  bool Start(Runnable* runnable = NULL);
+
+  // Tells the thread to stop and waits until it is joined.
+  // Never call Stop on the current thread.  Instead use the inherited Quit
+  // function which will exit the base MessageQueue without terminating the
+  // underlying OS thread.
+  virtual void Stop();
+
+  // By default, Thread::Run() calls ProcessMessages(kForever).  To do other
+  // work, override Run().  To receive and dispatch messages, call
+  // ProcessMessages occasionally.
+  virtual void Run();
+
+  virtual void Send(MessageHandler *phandler, uint32 id = 0,
+      MessageData *pdata = NULL);
+
+  // From MessageQueue
+  virtual void Clear(MessageHandler *phandler, uint32 id = MQID_ANY,
+                     MessageList* removed = NULL);
+  virtual void ReceiveSends();
+
+  // ProcessMessages will process I/O and dispatch messages until:
+  //  1) cms milliseconds have elapsed (returns true)
+  //  2) Stop() is called (returns false)
+  bool ProcessMessages(int cms);
+
+  // Returns true if this is a thread that we created using the standard
+  // constructor, false if it was created by a call to
+  // ThreadManager::WrapCurrentThread().  The main thread of an application
+  // is generally not owned, since the OS representation of the thread
+  // obviously exists before we can get to it.
+  // You cannot call Start on non-owned threads.
+  bool IsOwned();
+
+#ifdef WIN32
+  HANDLE GetHandle() {
+    return thread_;
+  }
+#elif POSIX
+  pthread_t GetPThread() {
+    return thread_;
+  }
+#endif
+
+private:
+  static void *PreRun(void *pv);
+  // Blocks the calling thread until this thread has terminated.
+  void Join();
+
+  std::list<_SendMessage> sendlist_;
+  std::string name_;
+  ThreadPriority priority_;
+  bool started_;
+  bool has_sends_;
+
+#ifdef POSIX
+  pthread_t thread_;
+#endif
+
+#ifdef WIN32
+  HANDLE thread_;
+#endif
+
+  bool owned_;
+
+  friend class ThreadManager;
+};
+
+// AutoThread automatically installs itself at construction
+// uninstalls at destruction, if a Thread object is
+// _not already_ associated with the current OS thread.
+
+class AutoThread : public Thread {
+public:
+  AutoThread(SocketServer* ss = 0);
+  virtual ~AutoThread();
+};
+
+// Win32 extension for threads that need to use COM
+#ifdef WIN32
+class ComThread : public Thread {
+ protected:
+  virtual void Run();
+};
+#endif
+
+// Provides an easy way to install/uninstall a socketserver on a thread.
+class SocketServerScope {
+ public:
+  explicit SocketServerScope(SocketServer* ss) {
+    old_ss_ = Thread::Current()->socketserver();
+    Thread::Current()->set_socketserver(ss);
+  }
+  ~SocketServerScope() {
+    Thread::Current()->set_socketserver(old_ss_);
+  }
+ private:
+  SocketServer* old_ss_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_THREAD_H_
diff --git a/talk/base/time.cc b/talk/base/time.cc
new file mode 100644
index 0000000..f5fa6db
--- /dev/null
+++ b/talk/base/time.cc
@@ -0,0 +1,125 @@
+/*
+ * 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.
+ */
+
+#ifdef POSIX
+#include <sys/time.h>
+#endif
+
+#ifdef WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#endif
+
+#include "talk/base/common.h"
+#include "talk/base/time.h"
+
+#define EFFICIENT_IMPLEMENTATION 1
+
+namespace talk_base {
+
+const uint32 LAST = 0xFFFFFFFF;
+const uint32 HALF = 0x80000000;
+
+#ifdef POSIX
+uint32 Time() {
+  struct timeval tv;
+  gettimeofday(&tv, 0);
+  return tv.tv_sec * 1000 + tv.tv_usec / 1000;
+}
+#endif
+
+#ifdef WIN32
+uint32 Time() {
+  return GetTickCount();
+}
+#endif
+
+uint32 StartTime() {
+  // Close to program execution time
+  static const uint32 g_start = Time();
+  return g_start;
+}
+
+// Make sure someone calls it so that it gets initialized
+static uint32 ignore = StartTime();
+
+uint32 TimeAfter(int32 elapsed) {
+  ASSERT(elapsed >= 0);
+  ASSERT(static_cast<uint32>(elapsed) < HALF);
+  return Time() + elapsed;
+}
+
+bool TimeIsBetween(uint32 earlier, uint32 middle, uint32 later) {
+  if (earlier <= later) {
+    return ((earlier <= middle) && (middle <= later));
+  } else {
+    return !((later < middle) && (middle < earlier));
+  }
+}
+
+bool TimeIsLaterOrEqual(uint32 earlier, uint32 later) {
+#if EFFICIENT_IMPLEMENTATION
+  int32 diff = later - earlier;
+  return (diff >= 0 && static_cast<uint32>(diff) < HALF);
+#else
+  const bool later_or_equal = TimeIsBetween(earlier, later, earlier + HALF);
+  return later_or_equal;
+#endif
+}
+
+bool TimeIsLater(uint32 earlier, uint32 later) {
+#if EFFICIENT_IMPLEMENTATION
+  int32 diff = later - earlier;
+  return (diff > 0 && static_cast<uint32>(diff) < HALF);
+#else
+  const bool earlier_or_equal = TimeIsBetween(later, earlier, later + HALF);
+  return !earlier_or_equal;
+#endif
+}
+
+int32 TimeDiff(uint32 later, uint32 earlier) {
+#if EFFICIENT_IMPLEMENTATION
+  return later - earlier;
+#else
+  const bool later_or_equal = TimeIsBetween(earlier, later, earlier + HALF);
+  if (later_or_equal) {
+    if (earlier <= later) {
+      return static_cast<long>(later - earlier);
+    } else {
+      return static_cast<long>(later + (LAST - earlier) + 1);
+    }
+  } else {
+    if (later <= earlier) {
+      return -static_cast<long>(earlier - later);
+    } else {
+      return -static_cast<long>(earlier + (LAST - later) + 1);
+    }
+  }
+#endif
+}
+
+} // namespace talk_base
diff --git a/talk/base/time.h b/talk/base/time.h
new file mode 100644
index 0000000..eca0e4e
--- /dev/null
+++ b/talk/base/time.h
@@ -0,0 +1,81 @@
+/*
+ * 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_BASE_TIME_H__
+#define TALK_BASE_TIME_H__
+
+#ifndef WIN32
+#include_next <time.h>
+#endif
+
+#include "talk/base/basictypes.h"
+
+namespace talk_base {
+
+typedef uint32 TimeStamp;
+
+// Returns the current time in milliseconds.
+uint32 Time();
+
+// Approximate time when the program started.
+uint32 StartTime();
+
+// Returns a future timestamp, 'elapsed' milliseconds from now.
+uint32 TimeAfter(int32 elapsed);
+
+// Comparisons between time values, which can wrap around.
+bool TimeIsBetween(uint32 earlier, uint32 middle, uint32 later);  // Inclusive
+bool TimeIsLaterOrEqual(uint32 earlier, uint32 later);  // Inclusive
+bool TimeIsLater(uint32 earlier, uint32 later);  // Exclusive
+
+// Returns the later of two timestamps.
+inline uint32 TimeMax(uint32 ts1, uint32 ts2) {
+  return TimeIsLaterOrEqual(ts1, ts2) ? ts2 : ts1;
+}
+
+// Returns the earlier of two timestamps.
+inline uint32 TimeMin(uint32 ts1, uint32 ts2) {
+  return TimeIsLaterOrEqual(ts1, ts2) ? ts1 : ts2;
+}
+
+// Number of milliseconds that would elapse between 'earlier' and 'later'
+// timestamps.  The value is negative if 'later' occurs before 'earlier'.
+int32 TimeDiff(uint32 later, uint32 earlier);
+
+// The number of milliseconds that have elapsed since 'earlier'.
+inline int32 TimeSince(uint32 earlier) {
+  return TimeDiff(Time(), earlier);
+} 
+
+// The number of milliseconds that will elapse between now and 'later'.
+inline int32 TimeUntil(uint32 later) {
+  return TimeDiff(later, Time());
+}
+
+} // namespace talk_base
+
+#endif // TALK_BASE_TIME_H__
diff --git a/talk/base/unixfilesystem.cc b/talk/base/unixfilesystem.cc
new file mode 100644
index 0000000..436dc2f
--- /dev/null
+++ b/talk/base/unixfilesystem.cc
@@ -0,0 +1,522 @@
+/*
+ * 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 "talk/base/unixfilesystem.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#ifdef OSX
+#include <Carbon/Carbon.h>
+#include <IOKit/IOCFBundle.h>
+#include <sys/statvfs.h>
+#include "talk/base/macutils.h"
+#endif  // OSX
+
+#if defined(POSIX) && !defined(OSX)
+#include <sys/types.h>
+#ifdef ANDROID
+#include <sys/statfs.h>
+#else
+#include <sys/statvfs.h>
+#endif  // ANDROID
+#include <pwd.h>
+#include <stdio.h>
+#include <unistd.h>
+#endif  // POSIX && !OSX
+
+#ifdef LINUX
+#include <ctype.h>
+#include <algorithm>
+#endif
+
+#include "talk/base/fileutils.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/stream.h"
+#include "talk/base/stringutils.h"
+
+#ifdef ANDROID
+namespace {
+// Android does not have a concept of a single temp dir shared by all
+// because resource are scarse on a phone. Instead each app gets some
+// space on the sdcard under a path that is given at runtime by the
+// system.
+// The disk allocation feature is still work in progress so currently
+// we return a hardcoded a path on the sdcard. In the future, we
+// should do a JNI call to get that info from the context.
+// TODO: Replace hardcoded path with a query to the Context
+// object to get the equivalents of '/tmp' and '~/.'
+
+// @return the folder for libjingle. Some extra path (typically
+// Google/<app name>) will be added.
+const char* GetAndroidAppDataFolder() {
+  return "/sdcard";
+}
+
+// @return the tmp folder to be used. Some extra path will be added to
+// that base folder.
+const char* GetAndroidTempFolder() {
+  return "/sdcard";
+}
+
+}  // anonymous namespace
+#endif
+
+namespace talk_base {
+
+std::string UnixFilesystem::app_temp_path_;
+
+bool UnixFilesystem::CreateFolder(const Pathname &path) {
+  std::string pathname(path.pathname());
+  int len = pathname.length();
+  if ((len == 0) || (pathname[len - 1] != '/'))
+    return false;
+
+  struct stat st;
+  int res = ::stat(pathname.c_str(), &st);
+  if (res == 0) {
+    // Something exists at this location, check if it is a directory
+    return S_ISDIR(st.st_mode) != 0;
+  } else if (errno != ENOENT) {
+    // Unexpected error
+    return false;
+  }
+
+  // Directory doesn't exist, look up one directory level
+  do {
+    --len;
+  } while ((len > 0) && (pathname[len - 1] != '/'));
+
+  if (!CreateFolder(Pathname(pathname.substr(0, len)))) {
+    return false;
+  }
+
+  LOG(LS_INFO) << "Creating folder: " << pathname;
+  return (0 == ::mkdir(pathname.c_str(), 0755));
+}
+
+FileStream *UnixFilesystem::OpenFile(const Pathname &filename,
+                                     const std::string &mode) {
+  FileStream *fs = new FileStream();
+  if (fs && !fs->Open(filename.pathname().c_str(), mode.c_str())) {
+    delete fs;
+    fs = NULL;
+  }
+  return fs;
+}
+
+bool UnixFilesystem::CreatePrivateFile(const Pathname &filename) {
+  int fd = open(filename.pathname().c_str(),
+                O_RDWR | O_CREAT | O_EXCL,
+                S_IRUSR | S_IWUSR);
+  if (fd < 0) {
+    LOG_ERR(LS_ERROR) << "open() failed.";
+    return false;
+  }
+  // Don't need to keep the file descriptor.
+  if (close(fd) < 0) {
+    LOG_ERR(LS_ERROR) << "close() failed.";
+    // Continue.
+  }
+  return true;
+}
+
+bool UnixFilesystem::DeleteFile(const Pathname &filename) {
+  LOG(LS_INFO) << "Deleting file:" << filename.pathname();
+
+  if (!IsFile(filename)) {
+    ASSERT(IsFile(filename));
+    return false;
+  }
+  return ::unlink(filename.pathname().c_str()) == 0;
+}
+
+bool UnixFilesystem::DeleteEmptyFolder(const Pathname &folder) {
+  LOG(LS_INFO) << "Deleting folder" << folder.pathname();
+
+  if (!IsFolder(folder)) {
+    ASSERT(IsFolder(folder));
+    return false;
+  }
+  std::string no_slash(folder.pathname(), 0, folder.pathname().length()-1);
+  return ::rmdir(no_slash.c_str()) == 0;
+}
+
+bool UnixFilesystem::GetTemporaryFolder(Pathname &pathname, bool create,
+                                        const std::string *append) {
+#ifdef OSX
+  FSRef fr;
+  if (0 != FSFindFolder(kOnAppropriateDisk, kTemporaryFolderType,
+                        kCreateFolder, &fr))
+    return false;
+  unsigned char buffer[NAME_MAX+1];
+  if (0 != FSRefMakePath(&fr, buffer, ARRAY_SIZE(buffer)))
+    return false;
+  pathname.SetPathname(reinterpret_cast<char*>(buffer), "");
+#elif defined(ANDROID)
+  pathname.SetPathname(GetAndroidTempFolder(), "");
+#else  // !OSX && !ANDROID
+  if (const char* tmpdir = getenv("TMPDIR")) {
+    pathname.SetPathname(tmpdir, "");
+  } else if (const char* tmp = getenv("TMP")) {
+    pathname.SetPathname(tmp, "");
+  } else {
+#ifdef P_tmpdir
+    pathname.SetPathname(P_tmpdir, "");
+#else  // !P_tmpdir
+    pathname.SetPathname("/tmp/", "");
+#endif  // !P_tmpdir
+  }
+#endif  // !OSX && !ANDROID
+  if (append) {
+    ASSERT(!append->empty());
+    pathname.AppendFolder(*append);
+  }
+  return !create || CreateFolder(pathname);
+}
+
+std::string UnixFilesystem::TempFilename(const Pathname &dir,
+                                         const std::string &prefix) {
+  int len = dir.pathname().size() + prefix.size() + 2 + 6;
+  char *tempname = new char[len];
+
+  snprintf(tempname, len, "%s/%sXXXXXX", dir.pathname().c_str(),
+           prefix.c_str());
+  int fd = ::mkstemp(tempname);
+  if (fd != -1)
+    ::close(fd);
+  std::string ret(tempname);
+  delete[] tempname;
+
+  return ret;
+}
+
+bool UnixFilesystem::MoveFile(const Pathname &old_path,
+                              const Pathname &new_path) {
+  if (!IsFile(old_path)) {
+    ASSERT(IsFile(old_path));
+    return false;
+  }
+  LOG(LS_VERBOSE) << "Moving " << old_path.pathname()
+                  << " to " << new_path.pathname();
+  if (rename(old_path.pathname().c_str(), new_path.pathname().c_str()) != 0) {
+    if (errno != EXDEV)
+      return false;
+    if (!CopyFile(old_path, new_path))
+      return false;
+    if (!DeleteFile(old_path))
+      return false;
+  }
+  return true;
+}
+
+bool UnixFilesystem::MoveFolder(const Pathname &old_path,
+                                const Pathname &new_path) {
+  if (!IsFolder(old_path)) {
+    ASSERT(IsFolder(old_path));
+    return false;
+  }
+  LOG(LS_VERBOSE) << "Moving " << old_path.pathname()
+                  << " to " << new_path.pathname();
+  if (rename(old_path.pathname().c_str(), new_path.pathname().c_str()) != 0) {
+    if (errno != EXDEV)
+      return false;
+    if (!CopyFolder(old_path, new_path))
+      return false;
+    if (!DeleteFolderAndContents(old_path))
+      return false;
+  }
+  return true;
+}
+
+bool UnixFilesystem::IsFolder(const Pathname &path) {
+  struct stat st;
+  if (stat(path.pathname().c_str(), &st) < 0)
+    return false;
+  return S_ISDIR(st.st_mode);
+}
+
+bool UnixFilesystem::CopyFile(const Pathname &old_path,
+                              const Pathname &new_path) {
+  LOG(LS_VERBOSE) << "Copying " << old_path.pathname()
+                  << " to " << new_path.pathname();
+  char buf[256];
+  size_t len;
+
+  StreamInterface *source = OpenFile(old_path, "rb");
+  if (!source)
+    return false;
+
+  StreamInterface *dest = OpenFile(new_path, "wb");
+  if (!dest) {
+    delete source;
+    return false;
+  }
+
+  while (source->Read(buf, sizeof(buf), &len, NULL) == SR_SUCCESS)
+    dest->Write(buf, len, NULL, NULL);
+
+  delete source;
+  delete dest;
+  return true;
+}
+
+bool UnixFilesystem::IsTemporaryPath(const Pathname& pathname) {
+  const char* const kTempPrefixes[] = {
+#ifdef ANDROID
+    GetAndroidTempFolder()
+#else
+    "/tmp/", "/var/tmp/",
+#ifdef OSX
+    "/private/tmp/", "/private/var/tmp/", "/private/var/folders/",
+#endif  // OSX
+#endif  // ANDROID
+  };
+  for (size_t i = 0; i < ARRAY_SIZE(kTempPrefixes); ++i) {
+    if (0 == strncmp(pathname.pathname().c_str(), kTempPrefixes[i],
+                     strlen(kTempPrefixes[i])))
+      return true;
+  }
+  return false;
+}
+
+bool UnixFilesystem::IsFile(const Pathname& pathname) {
+  struct stat st;
+  int res = ::stat(pathname.pathname().c_str(), &st);
+  // Treat symlinks, named pipes, etc. all as files.
+  return res == 0 && !S_ISDIR(st.st_mode);
+}
+
+bool UnixFilesystem::IsAbsent(const Pathname& pathname) {
+  struct stat st;
+  int res = ::stat(pathname.pathname().c_str(), &st);
+  // Note: we specifically maintain ENOTDIR as an error, because that implies
+  // that you could not call CreateFolder(pathname).
+  return res != 0 && ENOENT == errno;
+}
+
+bool UnixFilesystem::GetFileSize(const Pathname& pathname, size_t *size) {
+  struct stat st;
+  if (::stat(pathname.pathname().c_str(), &st) != 0)
+    return false;
+  *size = st.st_size;
+  return true;
+}
+
+bool UnixFilesystem::GetFileTime(const Pathname& path, FileTimeType which,
+                                 time_t* time) {
+  struct stat st;
+  if (::stat(path.pathname().c_str(), &st) != 0)
+    return false;
+  switch (which) {
+  case FTT_CREATED:
+    *time = st.st_ctime;
+    break;
+  case FTT_MODIFIED:
+    *time = st.st_mtime;
+    break;
+  case FTT_ACCESSED:
+    *time = st.st_atime;
+    break;
+  default:
+    return false;
+  }
+  return true;
+}
+
+bool UnixFilesystem::GetAppPathname(Pathname* path) {
+#ifdef OSX
+  ProcessSerialNumber psn = { 0, kCurrentProcess };
+  CFDictionaryRef procinfo = ProcessInformationCopyDictionary(&psn,
+      kProcessDictionaryIncludeAllInformationMask);
+  if (NULL == procinfo)
+    return false;
+  CFStringRef cfpath = (CFStringRef) CFDictionaryGetValue(procinfo,
+      kIOBundleExecutableKey);
+  std::string path8;
+  bool success = ToUtf8(cfpath, &path8);
+  CFRelease(procinfo);
+  if (success)
+    path->SetPathname(path8);
+  return success;
+#else  // OSX
+  char buffer[NAME_MAX+1];
+  size_t len = readlink("/proc/self/exe", buffer, ARRAY_SIZE(buffer) - 1);
+  if (len <= 0)
+    return false;
+  buffer[len] = '\0';
+  path->SetPathname(buffer);
+  return true;
+#endif  // OSX
+}
+
+bool UnixFilesystem::GetAppDataFolder(Pathname* path, bool per_user) {
+  ASSERT(!organization_name_.empty());
+  ASSERT(!application_name_.empty());
+
+  // First get the base directory for app data.
+#ifdef OSX
+  if (per_user) {
+    // Use ~/Library/Application Support/<orgname>/<appname>/
+    FSRef fr;
+    if (0 != FSFindFolder(kUserDomain, kApplicationSupportFolderType,
+                          kCreateFolder, &fr))
+      return false;
+    unsigned char buffer[NAME_MAX+1];
+    if (0 != FSRefMakePath(&fr, buffer, ARRAY_SIZE(buffer)))
+      return false;
+    path->SetPathname(reinterpret_cast<char*>(buffer), "");
+  } else {
+    // TODO
+    return false;
+  }
+#elif defined(ANDROID)  // && !OSX
+  // TODO: Check if the new disk allocation mechanism works
+  // per-user and we don't have the per_user distinction.
+  path->SetPathname(GetAndroidAppDataFolder(), "");
+#elif defined(LINUX)  // && !OSX && !defined(ANDROID)
+  if (per_user) {
+    // We follow the recommendations in
+    // http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
+    // It specifies separate directories for data and config files, but
+    // GetAppDataFolder() does not distinguish. We just return the config dir
+    // path.
+    const char* xdg_config_home = getenv("XDG_CONFIG_HOME");
+    if (xdg_config_home) {
+      path->SetPathname(xdg_config_home, "");
+    } else {
+      // XDG says to default to $HOME/.config. We also support falling back to
+      // other synonyms for HOME if for some reason it is not defined.
+      const char* homedir;
+      if (const char* home = getenv("HOME")) {
+        homedir = home;
+      } else if (const char* dotdir = getenv("DOTDIR")) {
+        homedir = dotdir;
+      } else if (passwd* pw = getpwuid(geteuid())) {
+        homedir = pw->pw_dir;
+      } else {
+        return false;
+      }
+      path->SetPathname(homedir, "");
+      path->AppendFolder(".config");
+    }
+  } else {
+    // XDG does not define a standard directory for writable global data. Let's
+    // just use this.
+    path->SetPathname("/var/cache/", "");
+  }
+#endif  // !OSX && !defined(ANDROID) && !defined(LINUX)
+
+  // Now add on a sub-path for our app.
+#if defined(OSX) || defined(ANDROID)
+  path->AppendFolder(organization_name_);
+  path->AppendFolder(application_name_);
+#elif defined(LINUX)
+  // XDG says to use a single directory level, so we concatenate the org and app
+  // name with a hyphen. We also do the Linuxy thing and convert to all
+  // lowercase with no spaces.
+  std::string subdir(organization_name_);
+  subdir.append("-");
+  subdir.append(application_name_);
+  replace_substrs(" ", 1, "", 0, &subdir);
+  std::transform(subdir.begin(), subdir.end(), subdir.begin(), ::tolower);
+  path->AppendFolder(subdir);
+#endif
+  return CreateFolder(*path);
+}
+
+bool UnixFilesystem::GetAppTempFolder(Pathname* path) {
+  ASSERT(!application_name_.empty());
+  // TODO: Consider whether we are worried about thread safety.
+  if (!app_temp_path_.empty()) {
+    path->SetPathname(app_temp_path_);
+    return true;
+  }
+
+  // Create a random directory as /tmp/<appname>-<pid>-<timestamp>
+  char buffer[128];
+  sprintfn(buffer, ARRAY_SIZE(buffer), "-%d-%d",
+           static_cast<int>(getpid()),
+           static_cast<int>(time(0)));
+  std::string folder(application_name_);
+  folder.append(buffer);
+  if (!GetTemporaryFolder(*path, true, &folder))
+    return false;
+
+  app_temp_path_ = path->pathname();
+  // TODO: atexit(DeleteFolderAndContents(app_temp_path_));
+  return true;
+}
+
+bool UnixFilesystem::GetDiskFreeSpace(const Pathname& path, int64 *freebytes) {
+  ASSERT(NULL != freebytes);
+  // TODO: Consider making relative paths absolute using cwd.
+  // TODO: When popping off a symlink, push back on the components of the
+  // symlink, so we don't jump out of the target disk inadvertently.
+  Pathname existing_path(path.folder(), "");
+  while (!existing_path.folder().empty() && IsAbsent(existing_path)) {
+    existing_path.SetFolder(existing_path.parent_folder());
+  }
+#ifdef ANDROID
+  struct statfs fs;
+  memset(&fs, 0, sizeof(fs));
+  if (0 != statfs(existing_path.pathname().c_str(), &fs))
+    return false;
+#else
+  struct statvfs vfs;
+  memset(&vfs, 0, sizeof(vfs));
+  if (0 != statvfs(existing_path.pathname().c_str(), &vfs))
+    return false;
+#endif  // ANDROID
+#ifdef LINUX
+  *freebytes = static_cast<int64>(vfs.f_bsize) * vfs.f_bavail;
+#elif defined(OSX)
+  *freebytes = static_cast<int64>(vfs.f_frsize) * vfs.f_bavail;
+#elif defined(ANDROID)
+  *freebytes = static_cast<int64>(fs.f_bsize) * fs.f_bavail;
+#endif
+
+  return true;
+}
+
+Pathname UnixFilesystem::GetCurrentDirectory() {
+  Pathname cwd;
+  char buffer[PATH_MAX];
+  char *path = getcwd(buffer, PATH_MAX);
+
+  if (!path) {
+    LOG_ERR(LS_ERROR) << "getcwd() failed";
+    return cwd;  // returns empty pathname
+  }
+  cwd.SetFolder(std::string(path));
+
+  return cwd;
+}
+
+}  // namespace talk_base
diff --git a/talk/base/unixfilesystem.h b/talk/base/unixfilesystem.h
new file mode 100644
index 0000000..71960ac
--- /dev/null
+++ b/talk/base/unixfilesystem.h
@@ -0,0 +1,114 @@
+/*
+ * 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_BASE_UNIXFILESYSTEM_H__
+#define _TALK_BASE_UNIXFILESYSTEM_H__
+
+#include "fileutils.h"
+
+namespace talk_base {
+
+class UnixFilesystem : public FilesystemInterface {
+ public:
+ 
+  // Opens a file. Returns an open StreamInterface if function succeeds. Otherwise,
+  // returns NULL.
+  virtual FileStream *OpenFile(const Pathname &filename, 
+                               const std::string &mode);
+
+  // Atomically creates an empty file accessible only to the current user if one
+  // does not already exist at the given path, otherwise fails.
+  virtual bool CreatePrivateFile(const Pathname &filename);
+
+  // This will attempt to delete the file located at filename.
+  // It will fail with VERIY if you pass it a non-existant file, or a directory.
+  virtual bool DeleteFile(const Pathname &filename);
+
+  // This will attempt to delete the folder located at 'folder'
+  // It ASSERTs and returns false if you pass it a non-existant folder or a plain file.
+  virtual bool DeleteEmptyFolder(const Pathname &folder);
+		
+  // Creates a directory. This will call itself recursively to create /foo/bar even if
+  // /foo does not exist.
+  // Returns TRUE if function succeeds
+   virtual bool CreateFolder(const Pathname &pathname);
+  
+  // This moves a file from old_path to new_path, where "file" can be a plain file
+  // or directory, which will be moved recursively.
+  // Returns true if function succeeds.
+  virtual bool MoveFile(const Pathname &old_path, const Pathname &new_path);
+  virtual bool MoveFolder(const Pathname &old_path, const Pathname &new_path);
+  
+  // This copies a file from old_path to _new_path where "file" can be a plain file
+  // or directory, which will be copied recursively.
+  // Returns true if function succeeds
+  virtual bool CopyFile(const Pathname &old_path, const Pathname &new_path);
+
+  // Returns true if a pathname is a directory
+  virtual bool IsFolder(const Pathname& pathname);
+
+  // Returns true if pathname represents a temporary location on the system.
+  virtual bool IsTemporaryPath(const Pathname& pathname);
+
+  // Returns true of pathname represents an existing file
+  virtual bool IsFile(const Pathname& pathname);
+  
+  // Returns true if pathname refers to no filesystem object, every parent
+  // directory either exists, or is also absent.
+  virtual bool IsAbsent(const Pathname& pathname);
+
+  virtual std::string TempFilename(const Pathname &dir, const std::string &prefix);
+
+  // A folder appropriate for storing temporary files (Contents are
+  // automatically deleted when the program exists)
+  virtual bool GetTemporaryFolder(Pathname &path, bool create,
+                                 const std::string *append);
+
+  virtual bool GetFileSize(const Pathname& path, size_t* size);
+  virtual bool GetFileTime(const Pathname& path, FileTimeType which,
+                           time_t* time);
+
+  // Returns the path to the running application.
+  virtual bool GetAppPathname(Pathname* path);
+
+  virtual bool GetAppDataFolder(Pathname* path, bool per_user);
+
+  // Get a temporary folder that is unique to the current user and application.
+  virtual bool GetAppTempFolder(Pathname* path);
+
+  virtual bool GetDiskFreeSpace(const Pathname& path, int64 *freebytes);
+
+  // Returns the absolute path of the current directory.
+  virtual Pathname GetCurrentDirectory();
+
+ private:
+  static std::string app_temp_path_;
+};
+
+}  // namespace talk_base
+
+#endif  // _UNIXFILESYSTEM_H__
diff --git a/talk/base/urlencode.cc b/talk/base/urlencode.cc
new file mode 100644
index 0000000..6dd51e1
--- /dev/null
+++ b/talk/base/urlencode.cc
@@ -0,0 +1,196 @@
+/*
+ * 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.
+ */
+
+#include "talk/base/urlencode.h"
+
+#include "talk/base/common.h"
+#include "talk/base/stringutils.h"
+
+static int HexPairValue(const char * code) {
+  int value = 0;
+  const char * pch = code;
+  for (;;) {
+    int digit = *pch++;
+    if (digit >= '0' && digit <= '9') {
+      value += digit - '0';
+    }
+    else if (digit >= 'A' && digit <= 'F') {
+      value += digit - 'A' + 10;
+    }
+    else if (digit >= 'a' && digit <= 'f') {
+      value += digit - 'a' + 10;
+    }
+    else {
+      return -1;
+    }
+    if (pch == code + 2)
+      return value;
+    value <<= 4;
+  }
+}
+
+int InternalUrlDecode(const char *source, char *dest,
+                      bool encode_space_as_plus) {
+  char * start = dest;
+
+  while (*source) {
+    switch (*source) {
+    case '+':
+      if (encode_space_as_plus) {
+        *(dest++) = ' ';
+      } else {
+        *dest++ = *source;
+      }
+      break;
+    case '%':
+      if (source[1] && source[2]) {
+        int value = HexPairValue(source + 1);
+        if (value >= 0) {
+          *(dest++) = value;
+          source += 2;
+        }
+        else {
+          *dest++ = '?';
+        }
+      }
+      else {
+        *dest++ = '?';
+      }
+      break;
+    default:
+      *dest++ = *source;
+    }
+    source++;
+  }
+
+  *dest = 0;
+  return dest - start;
+}
+
+int UrlDecode(const char *source, char *dest) {
+  return InternalUrlDecode(source, dest, true);
+}
+
+int UrlDecodeWithoutEncodingSpaceAsPlus(const char *source, char *dest) {
+  return InternalUrlDecode(source, dest, false);
+}
+
+bool IsValidUrlChar(char ch, bool unsafe_only) {
+  if (unsafe_only) {
+    return !(ch <= ' ' || strchr("\\\"^&`<>[]{}", ch));
+  } else {
+    return isalnum(ch) || strchr("-_.!~*'()", ch);
+  }
+}
+
+int InternalUrlEncode(const char *source, char *dest, unsigned int max,
+                      bool encode_space_as_plus, bool unsafe_only) {
+  static const char *digits = "0123456789ABCDEF";
+  if (max == 0) {
+    return 0;
+  }
+
+  char *start = dest;
+  while (static_cast<unsigned>(dest - start) < max && *source) {
+    unsigned char ch = static_cast<unsigned char>(*source);
+    if (*source == ' ' && encode_space_as_plus && !unsafe_only) {
+      *dest++ = '+';
+    } else if (IsValidUrlChar(ch, unsafe_only)) {
+      *dest++ = *source;
+    } else {
+      if (static_cast<unsigned>(dest - start) + 4 > max) {
+        break;
+      }
+      *dest++ = '%';
+      *dest++ = digits[(ch >> 4) & 0x0F];
+      *dest++ = digits[       ch & 0x0F];
+    }
+    source++;
+  }
+  ASSERT(static_cast<unsigned int>(dest - start) < max);
+  *dest = 0;
+
+  return dest - start;
+}
+
+int UrlEncode(const char *source, char *dest, unsigned max) {
+  return InternalUrlEncode(source, dest, max, true, false);
+}
+
+int UrlEncodeWithoutEncodingSpaceAsPlus(const char *source, char *dest,
+                                        unsigned max) {
+  return InternalUrlEncode(source, dest, max, false, false);
+}
+
+int UrlEncodeOnlyUnsafeChars(const char *source, char *dest, unsigned max) {
+  return InternalUrlEncode(source, dest, max, false, true);
+}
+
+std::string
+InternalUrlDecodeString(const std::string & encoded,
+                        bool encode_space_as_plus) {
+  size_t needed_length = encoded.length() + 1;
+  char* buf = STACK_ARRAY(char, needed_length);
+  InternalUrlDecode(encoded.c_str(), buf, encode_space_as_plus);
+  return buf;
+}
+
+std::string
+UrlDecodeString(const std::string & encoded) {
+  return InternalUrlDecodeString(encoded, true);
+}
+
+std::string
+UrlDecodeStringWithoutEncodingSpaceAsPlus(const std::string & encoded) {
+  return InternalUrlDecodeString(encoded, false);
+}
+
+std::string
+InternalUrlEncodeString(const std::string & decoded,
+                        bool encode_space_as_plus,
+                        bool unsafe_only) {
+  size_t needed_length = decoded.length() * 3 + 1;
+  char* buf = STACK_ARRAY(char, needed_length);
+  InternalUrlEncode(decoded.c_str(), buf, needed_length,
+                    encode_space_as_plus, unsafe_only);
+  return buf;
+}
+
+std::string
+UrlEncodeString(const std::string & decoded) {
+  return InternalUrlEncodeString(decoded, true, false);
+}
+
+std::string
+UrlEncodeStringWithoutEncodingSpaceAsPlus(const std::string & decoded) {
+  return InternalUrlEncodeString(decoded, false, false);
+}
+
+std::string
+UrlEncodeStringForOnlyUnsafeChars(const std::string & decoded) {
+  return InternalUrlEncodeString(decoded, false, true);
+}
diff --git a/talk/base/urlencode.h b/talk/base/urlencode.h
new file mode 100644
index 0000000..05165e8
--- /dev/null
+++ b/talk/base/urlencode.h
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+
+#ifndef _URLENCODE_H_
+#define _URLENCODE_H_ 
+
+#include <string>
+
+// Decode all encoded characters. Also decode + as space.
+int UrlDecode(const char *source, char *dest);
+
+// Decode all encoded characters.
+int UrlDecodeWithoutEncodingSpaceAsPlus(const char *source, char *dest);
+
+// Encode all characters except alphas, numbers, and -_.!~*'()
+// Also encode space as +.
+int UrlEncode(const char *source, char *dest, unsigned max);
+
+// Encode all characters except alphas, numbers, and -_.!~*'()
+int UrlEncodeWithoutEncodingSpaceAsPlus(const char *source, char *dest,
+                                        unsigned max);
+
+// Encode only unsafe chars, including \ "^&`<>[]{}
+// Also encode space as %20, instead of +
+int UrlEncodeOnlyUnsafeChars(const char *source, char *dest, unsigned max);
+
+std::string UrlDecodeString(const std::string & encoded);
+std::string UrlDecodeStringWithoutEncodingSpaceAsPlus(
+    const std::string & encoded);
+std::string UrlEncodeString(const std::string & decoded);
+std::string UrlEncodeStringWithoutEncodingSpaceAsPlus(
+    const std::string & decoded);
+std::string UrlEncodeStringForOnlyUnsafeChars(const std::string & decoded);
+
+#endif
+
diff --git a/talk/base/win32.cc b/talk/base/win32.cc
new file mode 100644
index 0000000..9a816c4
--- /dev/null
+++ b/talk/base/win32.cc
@@ -0,0 +1,183 @@
+/*
+ * 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/base/win32.h"
+#include <algorithm>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/common.h"
+
+namespace talk_base {
+
+//
+// Unix time is in seconds relative to 1/1/1970.  So we compute the windows
+// FILETIME of that time/date, then we add/subtract in appropriate units to
+// convert to/from unix time.
+// The units of FILETIME are 100ns intervals, so by multiplying by or dividing
+// by 10000000, we can convert to/from seconds.
+//
+// FileTime = UnixTime*10000000 + FileTime(1970)
+// UnixTime = (FileTime-FileTime(1970))/10000000
+//
+
+void FileTimeToUnixTime(const FILETIME& ft, time_t* ut) {
+  ASSERT(NULL != ut);
+
+  // FILETIME has an earlier date base than time_t (1/1/1970), so subtract off
+  // the difference.
+  SYSTEMTIME base_st;
+  memset(&base_st, 0, sizeof(base_st));
+  base_st.wDay = 1;
+  base_st.wMonth = 1;
+  base_st.wYear = 1970;
+
+  FILETIME base_ft;
+  SystemTimeToFileTime(&base_st, &base_ft);
+
+  ULARGE_INTEGER base_ul, current_ul;
+  memcpy(&base_ul, &base_ft, sizeof(FILETIME));
+  memcpy(&current_ul, &ft, sizeof(FILETIME));
+
+  // Divide by big number to convert to seconds, then subtract out the 1970
+  // base date value.
+  const ULONGLONG RATIO = 10000000;
+  *ut = static_cast<time_t>((current_ul.QuadPart - base_ul.QuadPart) / RATIO);
+}
+
+void UnixTimeToFileTime(const time_t& ut, FILETIME* ft) {
+  ASSERT(NULL != ft);
+
+  // FILETIME has an earlier date base than time_t (1/1/1970), so add in
+  // the difference.
+  SYSTEMTIME base_st;
+  memset(&base_st, 0, sizeof(base_st));
+  base_st.wDay = 1;
+  base_st.wMonth = 1;
+  base_st.wYear = 1970;
+
+  FILETIME base_ft;
+  SystemTimeToFileTime(&base_st, &base_ft);
+
+  ULARGE_INTEGER base_ul;
+  memcpy(&base_ul, &base_ft, sizeof(FILETIME));
+
+  // Multiply by big number to convert to 100ns units, then add in the 1970
+  // base date value.
+  const ULONGLONG RATIO = 10000000;
+  ULARGE_INTEGER current_ul;
+  current_ul.QuadPart = base_ul.QuadPart + static_cast<int64>(ut) * RATIO;
+  memcpy(ft, &current_ul, sizeof(FILETIME));
+}
+
+bool Utf8ToWindowsFilename(const std::string& utf8, std::wstring* filename) {
+  // TODO: Integrate into fileutils.h
+  // TODO: Handle wide and non-wide cases via TCHAR?
+  // TODO: Skip \\?\ processing if the length is not > MAX_PATH?
+  // TODO: Write unittests
+
+  // Convert to Utf16
+  int wlen = ::MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), utf8.length() + 1,
+                                   NULL, 0);
+  if (0 == wlen) {
+    return false;
+  }
+  wchar_t* wfilename = STACK_ARRAY(wchar_t, wlen);
+  if (0 == ::MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), utf8.length() + 1,
+                                 wfilename, wlen)) {
+    return false;
+  }
+  // Replace forward slashes with backslashes
+  std::replace(wfilename, wfilename + wlen, L'/', L'\\');
+  // Convert to complete filename
+  DWORD full_len = ::GetFullPathName(wfilename, 0, NULL, NULL);
+  if (0 == full_len) {
+    return false;
+  }
+  wchar_t* filepart = NULL;
+  wchar_t* full_filename = STACK_ARRAY(wchar_t, full_len + 6);
+  wchar_t* start = full_filename + 6;
+  if (0 == ::GetFullPathName(wfilename, full_len, start, &filepart)) {
+    return false;
+  }
+  // Add long-path prefix
+  const wchar_t kLongPathPrefix[] = L"\\\\?\\UNC";
+  if ((start[0] != L'\\') || (start[1] != L'\\')) {
+    // Non-unc path:     <pathname>
+    //      Becomes: \\?\<pathname>
+    start -= 4;
+    ASSERT(start >= full_filename);
+    memcpy(start, kLongPathPrefix, 4 * sizeof(wchar_t));
+  } else if (start[2] != L'?') {
+    // Unc path:       \\<server>\<pathname>
+    //  Becomes: \\?\UNC\<server>\<pathname>
+    start -= 6;
+    ASSERT(start >= full_filename);
+    memcpy(start, kLongPathPrefix, 7 * sizeof(wchar_t));
+  } else {
+    // Already in long-path form.
+  }
+  filename->assign(start);
+  return true;
+}
+
+bool GetOsVersion(int* major, int* minor, int* build) {
+  OSVERSIONINFO info = {0};
+  info.dwOSVersionInfoSize = sizeof(info);
+  if (GetVersionEx(&info)) {
+    if (major) *major = info.dwMajorVersion;
+    if (minor) *minor = info.dwMinorVersion;
+    if (build) *build = info.dwBuildNumber;
+    return true;
+  }
+  return false;
+}
+
+bool GetCurrentProcessIntegrityLevel(int* level) {
+  bool ret = false;
+  HANDLE process = GetCurrentProcess(), token;
+  if (OpenProcessToken(process, TOKEN_QUERY | TOKEN_QUERY_SOURCE, &token)) {
+    DWORD size;
+    if (!GetTokenInformation(token, TokenIntegrityLevel, NULL, 0, &size) &&
+        GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
+
+      char* buf = STACK_ARRAY(char, size);
+      TOKEN_MANDATORY_LABEL* til =
+          reinterpret_cast<TOKEN_MANDATORY_LABEL*>(buf);
+      if (GetTokenInformation(token, TokenIntegrityLevel, til, size, &size)) {
+
+        DWORD count = *GetSidSubAuthorityCount(til->Label.Sid);
+        *level = *GetSidSubAuthority(til->Label.Sid, count - 1);
+        ret = true;
+      }
+    }
+    CloseHandle(token);
+  }
+  return ret;
+}
+
+}  // namespace talk_base
+
diff --git a/talk/base/win32.h b/talk/base/win32.h
new file mode 100644
index 0000000..6274b0e
--- /dev/null
+++ b/talk/base/win32.h
@@ -0,0 +1,130 @@
+/*
+ * 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_BASE_WIN32_H_
+#define TALK_BASE_WIN32_H_
+
+#ifdef WIN32
+
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#include <winsock2.h>
+#include <windows.h>
+
+#ifndef SECURITY_MANDATORY_LABEL_AUTHORITY
+// Add defines that we use if we are compiling against older sdks
+#define SECURITY_MANDATORY_MEDIUM_RID               (0x00002000L)
+#define TokenIntegrityLevel static_cast<TOKEN_INFORMATION_CLASS>(0x19)
+typedef struct _TOKEN_MANDATORY_LABEL {
+    SID_AND_ATTRIBUTES Label;
+} TOKEN_MANDATORY_LABEL, *PTOKEN_MANDATORY_LABEL;
+#endif  // SECURITY_MANDATORY_LABEL_AUTHORITY
+
+#undef SetPort
+
+#include <string>
+
+#include "talk/base/stringutils.h"
+#include "talk/base/basictypes.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+
+inline std::wstring ToUtf16(const char* utf8, size_t len) {
+  int len16 = ::MultiByteToWideChar(CP_UTF8, 0, utf8, len, NULL, 0);
+  wchar_t* ws = STACK_ARRAY(wchar_t, len16);
+  ::MultiByteToWideChar(CP_UTF8, 0, utf8, len, ws, len16);
+  return std::wstring(ws, len16);
+}
+
+inline std::wstring ToUtf16(const std::string& str) {
+  return ToUtf16(str.data(), str.length());
+}
+
+inline std::string ToUtf8(const wchar_t* wide, size_t len) {
+  int len8 = ::WideCharToMultiByte(CP_UTF8, 0, wide, len, NULL, 0, NULL, NULL);
+  char* ns = STACK_ARRAY(char, len8);
+  ::WideCharToMultiByte(CP_UTF8, 0, wide, len, ns, len8, NULL, NULL);
+  return std::string(ns, len8);
+}
+
+inline std::string ToUtf8(const std::wstring& wstr) {
+  return ToUtf8(wstr.data(), wstr.length());
+}
+
+// Convert FILETIME to time_t
+void FileTimeToUnixTime(const FILETIME& ft, time_t* ut);
+
+// Convert time_t to FILETIME
+void UnixTimeToFileTime(const time_t& ut, FILETIME * ft);
+
+// Convert a Utf8 path representation to a non-length-limited Unicode pathname.
+bool Utf8ToWindowsFilename(const std::string& utf8, std::wstring* filename);
+
+// Convert a FILETIME to a UInt64
+inline uint64 ToUInt64(const FILETIME& ft) {
+  ULARGE_INTEGER r = {ft.dwLowDateTime, ft.dwHighDateTime};
+  return r.QuadPart;
+}
+
+enum WindowsMajorVersions {
+  kWindows2000 = 5,
+  kWindowsVista = 6,
+};
+bool GetOsVersion(int* major, int* minor, int* build);
+
+inline bool IsWindowsVistaOrLater() {
+  int major;
+  return (GetOsVersion(&major, NULL, NULL) && major >= kWindowsVista);
+}
+
+inline bool IsWindowsXpOrLater() {
+  int major, minor;
+  return (GetOsVersion(&major, &minor, NULL) &&
+          (major >= kWindowsVista ||
+          (major == kWindows2000 && minor >= 1)));
+}
+
+// Determine the current integrity level of the process.
+bool GetCurrentProcessIntegrityLevel(int* level);
+
+inline bool IsCurrentProcessLowIntegrity() {
+  int level;
+  return (GetCurrentProcessIntegrityLevel(&level) &&
+      level < SECURITY_MANDATORY_MEDIUM_RID);
+}
+
+bool AdjustCurrentProcessPrivilege(const TCHAR* privilege, bool to_enable);
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif  // WIN32
+#endif  // TALK_BASE_WIN32_H_
diff --git a/talk/base/win32filesystem.cc b/talk/base/win32filesystem.cc
new file mode 100644
index 0000000..f2a2256
--- /dev/null
+++ b/talk/base/win32filesystem.cc
@@ -0,0 +1,477 @@
+/*
+ * 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 "talk/base/win32filesystem.h"
+
+#include "talk/base/win32.h"
+#include <shellapi.h>
+#include <shlobj.h>
+#include <tchar.h>
+
+#include "talk/base/fileutils.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stream.h"
+#include "talk/base/stringutils.h"
+
+// In several places in this file, we test the integrity level of the process
+// before calling GetLongPathName. We do this because calling GetLongPathName
+// when running under protected mode IE (a low integrity process) can result in
+// a virtualized path being returned, which is wrong if you only plan to read.
+// TODO: Waiting to hear back from IE team on whether this is the
+// best approach; IEIsProtectedModeProcess is another possible solution.
+
+namespace talk_base {
+
+bool Win32Filesystem::CreateFolder(const Pathname &pathname) {
+  if (pathname.pathname().empty() || !pathname.filename().empty())
+    return false;
+
+  std::wstring path16;
+  if (!Utf8ToWindowsFilename(pathname.pathname(), &path16))
+    return false;
+
+  DWORD res = ::GetFileAttributes(path16.c_str());
+  if (res != INVALID_FILE_ATTRIBUTES) {
+    // Something exists at this location, check if it is a directory
+    return ((res & FILE_ATTRIBUTE_DIRECTORY) != 0);
+  } else if ((GetLastError() != ERROR_FILE_NOT_FOUND)
+              && (GetLastError() != ERROR_PATH_NOT_FOUND)) {
+    // Unexpected error
+    return false;
+  }
+
+  // Directory doesn't exist, look up one directory level
+  if (!pathname.parent_folder().empty()) {
+    Pathname parent(pathname);
+    parent.SetFolder(pathname.parent_folder());
+    if (!CreateFolder(parent)) {
+      return false;
+    }
+  }
+
+  return (::CreateDirectory(path16.c_str(), NULL) != 0);
+}
+
+FileStream *Win32Filesystem::OpenFile(const Pathname &filename,
+                                      const std::string &mode) {
+  FileStream *fs = new FileStream();
+  if (fs && !fs->Open(filename.pathname().c_str(), mode.c_str())) {
+    delete fs;
+    fs = NULL;
+  }
+  return fs;
+}
+
+bool Win32Filesystem::CreatePrivateFile(const Pathname &filename) {
+  // To make the file private to the current user, we first must construct a
+  // SECURITY_DESCRIPTOR specifying an ACL. This code is mostly based upon
+  // http://msdn.microsoft.com/en-us/library/ms707085%28VS.85%29.aspx
+
+  // Get the current process token.
+  HANDLE process_token = INVALID_HANDLE_VALUE;
+  if (!::OpenProcessToken(GetCurrentProcess(),
+                          TOKEN_QUERY,
+                          &process_token)) {
+    LOG_ERR(LS_ERROR) << "OpenProcessToken() failed";
+    return false;
+  }
+
+  // Get the size of its TOKEN_USER structure. Return value is not checked
+  // because we expect it to fail.
+  DWORD token_user_size = 0;
+  (void)::GetTokenInformation(process_token,
+                              TokenUser,
+                              NULL,
+                              0,
+                              &token_user_size);
+
+  // Get the TOKEN_USER structure.
+  scoped_array<char> token_user_bytes(new char[token_user_size]);
+  PTOKEN_USER token_user = reinterpret_cast<PTOKEN_USER>(
+      token_user_bytes.get());
+  memset(token_user, 0, token_user_size);
+  BOOL success = ::GetTokenInformation(process_token,
+                                       TokenUser,
+                                       token_user,
+                                       token_user_size,
+                                       &token_user_size);
+  // We're now done with this.
+  ::CloseHandle(process_token);
+  if (!success) {
+    LOG_ERR(LS_ERROR) << "GetTokenInformation() failed";
+    return false;
+  }
+
+  if (!IsValidSid(token_user->User.Sid)) {
+    LOG_ERR(LS_ERROR) << "Current process has invalid user SID";
+    return false;
+  }
+
+  // Compute size needed for an ACL that allows access to just this user.
+  int acl_size = sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) +
+      GetLengthSid(token_user->User.Sid);
+
+  // Allocate it.
+  scoped_array<char> acl_bytes(new char[acl_size]);
+  PACL acl = reinterpret_cast<PACL>(acl_bytes.get());
+  memset(acl, 0, acl_size);
+  if (!::InitializeAcl(acl, acl_size, ACL_REVISION)) {
+    LOG_ERR(LS_ERROR) << "InitializeAcl() failed";
+    return false;
+  }
+
+  // Allow access to only the current user.
+  if (!::AddAccessAllowedAce(acl,
+                             ACL_REVISION,
+                             GENERIC_READ | GENERIC_WRITE | STANDARD_RIGHTS_ALL,
+                             token_user->User.Sid)) {
+    LOG_ERR(LS_ERROR) << "AddAccessAllowedAce() failed";
+    return false;
+  }
+
+  // Now make the security descriptor.
+  SECURITY_DESCRIPTOR security_descriptor;
+  if (!::InitializeSecurityDescriptor(&security_descriptor,
+                                      SECURITY_DESCRIPTOR_REVISION)) {
+    LOG_ERR(LS_ERROR) << "InitializeSecurityDescriptor() failed";
+    return false;
+  }
+
+  // Put the ACL in it.
+  if (!::SetSecurityDescriptorDacl(&security_descriptor,
+                                   TRUE,
+                                   acl,
+                                   FALSE)) {
+    LOG_ERR(LS_ERROR) << "SetSecurityDescriptorDacl() failed";
+    return false;
+  }
+
+  // Finally create the file.
+  SECURITY_ATTRIBUTES security_attributes;
+  security_attributes.nLength = sizeof(security_attributes);
+  security_attributes.lpSecurityDescriptor = &security_descriptor;
+  security_attributes.bInheritHandle = FALSE;
+  HANDLE handle = ::CreateFile(
+      ToUtf16(filename.pathname()).c_str(),
+      GENERIC_READ | GENERIC_WRITE,
+      FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
+      &security_attributes,
+      CREATE_NEW,
+      0,
+      NULL);
+  if (INVALID_HANDLE_VALUE == handle) {
+    LOG_ERR(LS_ERROR) << "CreateFile() failed";
+    return false;
+  }
+  if (!::CloseHandle(handle)) {
+    LOG_ERR(LS_ERROR) << "CloseFile() failed";
+    // Continue.
+  }
+  return true;
+}
+
+bool Win32Filesystem::DeleteFile(const Pathname &filename) {
+  LOG(LS_INFO) << "Deleting file " << filename.pathname();
+  if (!IsFile(filename)) {
+    ASSERT(IsFile(filename));
+    return false;
+  }
+  return ::DeleteFile(ToUtf16(filename.pathname()).c_str()) != 0;
+}
+
+bool Win32Filesystem::DeleteEmptyFolder(const Pathname &folder) {
+  LOG(LS_INFO) << "Deleting folder " << folder.pathname();
+
+  std::string no_slash(folder.pathname(), 0, folder.pathname().length()-1);
+  return ::RemoveDirectory(ToUtf16(no_slash).c_str()) != 0;
+}
+
+bool Win32Filesystem::GetTemporaryFolder(Pathname &pathname, bool create,
+                                         const std::string *append) {
+  wchar_t buffer[MAX_PATH + 1];
+  if (!::GetTempPath(ARRAY_SIZE(buffer), buffer))
+    return false;
+  if (!IsCurrentProcessLowIntegrity() &&
+      !::GetLongPathName(buffer, buffer, ARRAY_SIZE(buffer)))
+    return false;
+  size_t len = strlen(buffer);
+  if ((len > 0) && (buffer[len-1] != '\\')) {
+    len += strcpyn(buffer + len, ARRAY_SIZE(buffer) - len, L"\\");
+  }
+  if (len >= ARRAY_SIZE(buffer) - 1)
+    return false;
+  pathname.clear();
+  pathname.SetFolder(ToUtf8(buffer));
+  if (append != NULL) {
+    ASSERT(!append->empty());
+    pathname.AppendFolder(*append);
+  }
+  return !create || CreateFolder(pathname);
+}
+
+std::string Win32Filesystem::TempFilename(const Pathname &dir,
+                                          const std::string &prefix) {
+  wchar_t filename[MAX_PATH];
+  if (::GetTempFileName(ToUtf16(dir.pathname()).c_str(),
+                        ToUtf16(prefix).c_str(), 0, filename) != 0)
+    return ToUtf8(filename);
+  ASSERT(false);
+  return "";
+}
+
+bool Win32Filesystem::MoveFile(const Pathname &old_path,
+                               const Pathname &new_path) {
+  if (!IsFile(old_path)) {
+    ASSERT(IsFile(old_path));
+    return false;
+  }
+  LOG(LS_INFO) << "Moving " << old_path.pathname()
+               << " to " << new_path.pathname();
+  return ::MoveFile(ToUtf16(old_path.pathname()).c_str(),
+                    ToUtf16(new_path.pathname()).c_str()) != 0;
+}
+
+bool Win32Filesystem::MoveFolder(const Pathname &old_path,
+                                 const Pathname &new_path) {
+  if (!IsFolder(old_path)) {
+    ASSERT(IsFolder(old_path));
+    return false;
+  }
+  LOG(LS_INFO) << "Moving " << old_path.pathname()
+               << " to " << new_path.pathname();
+  if (::MoveFile(ToUtf16(old_path.pathname()).c_str(),
+               ToUtf16(new_path.pathname()).c_str()) == 0) {
+    if (::GetLastError() != ERROR_NOT_SAME_DEVICE) {
+      LOG_GLE(LS_ERROR) << "Failed to move file";
+      return false;
+    }
+    if (!CopyFolder(old_path, new_path))
+      return false;
+    if (!DeleteFolderAndContents(old_path))
+      return false;
+  }
+  return true;
+}
+
+bool Win32Filesystem::IsFolder(const Pathname &path) {
+  WIN32_FILE_ATTRIBUTE_DATA data = {0};
+  if (0 == ::GetFileAttributesEx(ToUtf16(path.pathname()).c_str(),
+                                 GetFileExInfoStandard, &data))
+    return false;
+  return (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ==
+      FILE_ATTRIBUTE_DIRECTORY;
+}
+
+bool Win32Filesystem::IsFile(const Pathname &path) {
+  WIN32_FILE_ATTRIBUTE_DATA data = {0};
+  if (0 == ::GetFileAttributesEx(ToUtf16(path.pathname()).c_str(),
+                                 GetFileExInfoStandard, &data))
+    return false;
+  return (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
+}
+
+bool Win32Filesystem::IsAbsent(const Pathname& path) {
+  WIN32_FILE_ATTRIBUTE_DATA data = {0};
+  if (0 != ::GetFileAttributesEx(ToUtf16(path.pathname()).c_str(),
+                                 GetFileExInfoStandard, &data))
+    return false;
+  DWORD err = ::GetLastError();
+  return (ERROR_FILE_NOT_FOUND == err || ERROR_PATH_NOT_FOUND == err);
+}
+
+bool Win32Filesystem::CopyFile(const Pathname &old_path,
+                               const Pathname &new_path) {
+  return ::CopyFile(ToUtf16(old_path.pathname()).c_str(),
+                    ToUtf16(new_path.pathname()).c_str(), TRUE) != 0;
+}
+
+bool Win32Filesystem::IsTemporaryPath(const Pathname& pathname) {
+  TCHAR buffer[MAX_PATH + 1];
+  if (!::GetTempPath(ARRAY_SIZE(buffer), buffer))
+    return false;
+  if (!IsCurrentProcessLowIntegrity() &&
+      !::GetLongPathName(buffer, buffer, ARRAY_SIZE(buffer)))
+    return false;
+  return (::strnicmp(ToUtf16(pathname.pathname()).c_str(),
+                     buffer, strlen(buffer)) == 0);
+}
+
+bool Win32Filesystem::GetFileSize(const Pathname &pathname, size_t *size) {
+  WIN32_FILE_ATTRIBUTE_DATA data = {0};
+  if (::GetFileAttributesEx(ToUtf16(pathname.pathname()).c_str(),
+                            GetFileExInfoStandard, &data) == 0)
+  return false;
+  *size = data.nFileSizeLow;
+  return true;
+}
+
+bool Win32Filesystem::GetFileTime(const Pathname& path, FileTimeType which,
+                                  time_t* time) {
+  WIN32_FILE_ATTRIBUTE_DATA data = {0};
+  if (::GetFileAttributesEx(ToUtf16(path.pathname()).c_str(),
+                            GetFileExInfoStandard, &data) == 0)
+    return false;
+  switch (which) {
+  case FTT_CREATED:
+    FileTimeToUnixTime(data.ftCreationTime, time);
+    break;
+  case FTT_MODIFIED:
+    FileTimeToUnixTime(data.ftLastWriteTime, time);
+    break;
+  case FTT_ACCESSED:
+    FileTimeToUnixTime(data.ftLastAccessTime, time);
+    break;
+  default:
+    return false;
+  }
+  return true;
+}
+
+bool Win32Filesystem::GetAppPathname(Pathname* path) {
+  TCHAR buffer[MAX_PATH + 1];
+  if (0 == ::GetModuleFileName(NULL, buffer, ARRAY_SIZE(buffer)))
+    return false;
+  path->SetPathname(ToUtf8(buffer));
+  return true;
+}
+
+bool Win32Filesystem::GetAppDataFolder(Pathname* path, bool per_user) {
+  ASSERT(!organization_name_.empty());
+  ASSERT(!application_name_.empty());
+  TCHAR buffer[MAX_PATH + 1];
+  int csidl = per_user ? CSIDL_LOCAL_APPDATA : CSIDL_COMMON_APPDATA;
+  if (!::SHGetSpecialFolderPath(NULL, buffer, csidl, TRUE))
+    return false;
+  if (!IsCurrentProcessLowIntegrity() &&
+      !::GetLongPathName(buffer, buffer, ARRAY_SIZE(buffer)))
+    return false;
+  size_t len = strcatn(buffer, ARRAY_SIZE(buffer), __T("\\"));
+  len += strcpyn(buffer + len, ARRAY_SIZE(buffer) - len,
+                 ToUtf16(organization_name_).c_str());
+  if ((len > 0) && (buffer[len-1] != __T('\\'))) {
+    len += strcpyn(buffer + len, ARRAY_SIZE(buffer) - len, __T("\\"));
+  }
+  len += strcpyn(buffer + len, ARRAY_SIZE(buffer) - len,
+                 ToUtf16(application_name_).c_str());
+  if ((len > 0) && (buffer[len-1] != __T('\\'))) {
+    len += strcpyn(buffer + len, ARRAY_SIZE(buffer) - len, __T("\\"));
+  }
+  if (len >= ARRAY_SIZE(buffer) - 1)
+    return false;
+  path->clear();
+  path->SetFolder(ToUtf8(buffer));
+  return CreateFolder(*path);
+}
+
+bool Win32Filesystem::GetAppTempFolder(Pathname* path) {
+  if (!GetAppPathname(path))
+    return false;
+  std::string filename(path->filename());
+  return GetTemporaryFolder(*path, true, &filename);
+}
+
+bool Win32Filesystem::GetDiskFreeSpace(const Pathname& path, int64 *freebytes) {
+  if (!freebytes) {
+    return false;
+  }
+  char drive[4];
+  std::wstring drive16;
+  const wchar_t* target_drive = NULL;
+  if (path.GetDrive(drive, sizeof(drive))) {
+    drive16 = ToUtf16(drive);
+    target_drive = drive16.c_str();
+  } else if (path.folder().substr(0, 2) == "\\\\") {
+    // UNC path, fail.
+    // TODO: Handle UNC paths.
+    return false;
+  } else {
+    // The path is probably relative.  GetDriveType and GetDiskFreeSpaceEx
+    // use the current drive if NULL is passed as the drive name.
+    // TODO: Add method to Pathname to determine if the path is relative.
+    // TODO: Add method to Pathname to convert a path to absolute.
+  }
+  UINT driveType = ::GetDriveType(target_drive);
+  if ( (driveType & DRIVE_REMOTE) || (driveType & DRIVE_UNKNOWN) ) {
+    LOG(LS_VERBOSE) << " remove or unknown drive " << drive;
+    return false;
+  }
+
+  int64 totalNumberOfBytes;  // receives the number of bytes on disk
+  int64 totalNumberOfFreeBytes;  // receives the free bytes on disk
+  // make sure things won't change in 64 bit machine
+  // TODO replace with compile time assert
+  ASSERT(sizeof(ULARGE_INTEGER) == sizeof(uint64));  //NOLINT
+  if (::GetDiskFreeSpaceEx(target_drive,
+                           (PULARGE_INTEGER)freebytes,
+                           (PULARGE_INTEGER)&totalNumberOfBytes,
+                           (PULARGE_INTEGER)&totalNumberOfFreeBytes)) {
+    return true;
+  } else {
+    LOG(LS_VERBOSE) << " GetDiskFreeSpaceEx returns error ";
+    return false;
+  }
+}
+
+Pathname Win32Filesystem::GetCurrentDirectory() {
+  Pathname cwd;
+  int path_len = 0;
+  scoped_array<wchar_t> path;
+  do {
+    int needed = ::GetCurrentDirectory(path_len, path.get());
+    if (needed == 0) {
+      // Error.
+      LOG_GLE(LS_ERROR) << "::GetCurrentDirectory() failed";
+      return cwd;  // returns empty pathname
+    }
+    if (needed <= path_len) {
+      // It wrote successfully.
+      break;
+    }
+    // Else need to re-alloc for "needed".
+    path.reset(new wchar_t[needed]);
+    path_len = needed;
+  } while (true);
+  cwd.SetFolder(ToUtf8(path.get()));
+  return cwd;
+}
+
+// TODO: Consider overriding DeleteFolderAndContents for speed and potentially
+// better OS integration (recycle bin?)
+/*
+  std::wstring temp_path16 = ToUtf16(temp_path.pathname());
+  temp_path16.append(1, '*');
+  temp_path16.append(1, '\0');
+
+  SHFILEOPSTRUCT file_op = { 0 };
+  file_op.wFunc = FO_DELETE;
+  file_op.pFrom = temp_path16.c_str();
+  file_op.fFlags = FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT;
+  return (0 == SHFileOperation(&file_op));
+*/
+
+}  // namespace talk_base
diff --git a/talk/base/win32filesystem.h b/talk/base/win32filesystem.h
new file mode 100644
index 0000000..c17bdd9
--- /dev/null
+++ b/talk/base/win32filesystem.h
@@ -0,0 +1,118 @@
+/*
+ * 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_BASE_WIN32FILESYSTEM_H__
+#define _TALK_BASE_WIN32FILESYSTEM_H__
+
+#include "fileutils.h"
+
+namespace talk_base {
+
+class Win32Filesystem : public FilesystemInterface {
+ public:
+  // Opens a file. Returns an open StreamInterface if function succeeds. Otherwise,
+  // returns NULL.
+  virtual FileStream *OpenFile(const Pathname &filename, 
+                               const std::string &mode);
+
+  // Atomically creates an empty file accessible only to the current user if one
+  // does not already exist at the given path, otherwise fails.
+  virtual bool CreatePrivateFile(const Pathname &filename);
+
+  // This will attempt to delete the path located at filename.
+  // If the path points to a folder, it will fail with VERIFY
+  virtual bool DeleteFile(const Pathname &filename);
+
+  // This will attempt to delete an empty folder. If the path does not point to
+  // a folder, it fails with VERIFY. If the folder is not empty, it fails normally
+  virtual bool DeleteEmptyFolder(const Pathname &folder);
+
+  // Creates a directory. This will call itself recursively to create /foo/bar even if
+  // /foo does not exist.
+  // Returns TRUE if function succeeds
+  virtual bool CreateFolder(const Pathname &pathname);
+  
+  // This moves a file from old_path to new_path. If the new path is on a 
+  // different volume than the old, it will attempt to copy and then delete
+  // the folder
+  // Returns true if the file is successfully moved
+  virtual bool MoveFile(const Pathname &old_path, const Pathname &new_path);
+  
+  // Moves a folder from old_path to new_path. If the new path is on a different
+  // volume from the old, it will attempt to Copy and then Delete the folder
+  // Returns true if the folder is successfully moved
+  virtual bool MoveFolder(const Pathname &old_path, const Pathname &new_path);
+  
+  // This copies a file from old_path to _new_path
+  // Returns true if function succeeds
+  virtual bool CopyFile(const Pathname &old_path, const Pathname &new_path);
+
+  // Returns true if a pathname is a directory
+  virtual bool IsFolder(const Pathname& pathname);
+  
+  // Returns true if a file exists at path
+  virtual bool IsFile(const Pathname &path);
+
+  // Returns true if pathname refers to no filesystem object, every parent
+  // directory either exists, or is also absent.
+  virtual bool IsAbsent(const Pathname& pathname);
+
+  // Returns true if pathname represents a temporary location on the system.
+  virtual bool IsTemporaryPath(const Pathname& pathname);
+
+  // All of the following functions set pathname and return true if successful.
+  // Returned paths always include a trailing backslash.
+  // If create is true, the path will be recursively created.
+  // If append is non-NULL, it will be appended (and possibly created).
+
+  virtual std::string TempFilename(const Pathname &dir, const std::string &prefix);
+
+  virtual bool GetFileSize(const Pathname& path, size_t* size);
+  virtual bool GetFileTime(const Pathname& path, FileTimeType which,
+                           time_t* time);
+ 
+  // A folder appropriate for storing temporary files (Contents are
+  // automatically deleted when the program exists)
+  virtual bool GetTemporaryFolder(Pathname &path, bool create,
+                                 const std::string *append);
+
+  // Returns the path to the running application.
+  virtual bool GetAppPathname(Pathname* path);
+
+  virtual bool GetAppDataFolder(Pathname* path, bool per_user);
+
+  // Get a temporary folder that is unique to the current user and application.
+  virtual bool GetAppTempFolder(Pathname* path);
+
+  virtual bool GetDiskFreeSpace(const Pathname& path, int64 *freebytes);
+
+  virtual Pathname GetCurrentDirectory();
+};
+
+}  // namespace talk_base
+
+#endif  // _WIN32FILESYSTEM_H__
diff --git a/talk/base/win32securityerrors.cc b/talk/base/win32securityerrors.cc
new file mode 100644
index 0000000..50f4f66
--- /dev/null
+++ b/talk/base/win32securityerrors.cc
@@ -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.
+ */
+
+#include "talk/base/win32.h"
+#include "talk/base/logging.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+
+extern const ConstantLabel SECURITY_ERRORS[];
+
+const ConstantLabel SECURITY_ERRORS[] = {
+  KLABEL(SEC_I_COMPLETE_AND_CONTINUE),
+  KLABEL(SEC_I_COMPLETE_NEEDED),
+  KLABEL(SEC_I_CONTEXT_EXPIRED),
+  KLABEL(SEC_I_CONTINUE_NEEDED),
+  KLABEL(SEC_I_INCOMPLETE_CREDENTIALS),
+  KLABEL(SEC_I_RENEGOTIATE),
+  KLABEL(SEC_E_CERT_EXPIRED),
+  KLABEL(SEC_E_INCOMPLETE_MESSAGE),
+  KLABEL(SEC_E_INSUFFICIENT_MEMORY),
+  KLABEL(SEC_E_INTERNAL_ERROR),
+  KLABEL(SEC_E_INVALID_HANDLE),
+  KLABEL(SEC_E_INVALID_TOKEN),
+  KLABEL(SEC_E_LOGON_DENIED),
+  KLABEL(SEC_E_NO_AUTHENTICATING_AUTHORITY),
+  KLABEL(SEC_E_NO_CREDENTIALS),
+  KLABEL(SEC_E_NOT_OWNER),
+  KLABEL(SEC_E_OK),
+  KLABEL(SEC_E_SECPKG_NOT_FOUND),
+  KLABEL(SEC_E_TARGET_UNKNOWN),
+  KLABEL(SEC_E_UNKNOWN_CREDENTIALS),
+  KLABEL(SEC_E_UNSUPPORTED_FUNCTION),
+  KLABEL(SEC_E_UNTRUSTED_ROOT),
+  KLABEL(SEC_E_WRONG_PRINCIPAL),
+  LASTLABEL
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
diff --git a/talk/base/win32socketinit.cc b/talk/base/win32socketinit.cc
new file mode 100644
index 0000000..f6ac666
--- /dev/null
+++ b/talk/base/win32socketinit.cc
@@ -0,0 +1,63 @@
+/*
+ * libjingle
+ * Copyright 2009, 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/base/win32socketinit.h"
+
+#include "talk/base/win32.h"
+
+namespace talk_base {
+
+// Please don't remove this function.
+void EnsureWinsockInit() {
+  // The default implementation uses a global initializer, so WSAStartup
+  // happens at module load time.  Thus we don't need to do anything here.
+  // The hook is provided so that a client that statically links with
+  // libjingle can override it, to provide its own initialization.
+}
+
+#ifdef WIN32
+class WinsockInitializer {
+ public:
+  WinsockInitializer() {
+    WSADATA wsaData;
+    WORD wVersionRequested = MAKEWORD(1, 0);
+    err_ = WSAStartup(wVersionRequested, &wsaData);
+  }
+  ~WinsockInitializer() {
+    if (!err_)
+      WSACleanup();
+  }
+  int error() {
+    return err_;
+  }
+ private:
+  int err_;
+};
+WinsockInitializer g_winsockinit;
+#endif
+
+}  // namespace talk_base
diff --git a/talk/base/win32socketinit.h b/talk/base/win32socketinit.h
new file mode 100644
index 0000000..f56b7ff
--- /dev/null
+++ b/talk/base/win32socketinit.h
@@ -0,0 +1,37 @@
+/*
+ * libjingle
+ * Copyright 2009, 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_BASE_WIN32SOCKETINIT_H_
+#define TALK_BASE_WIN32SOCKETINIT_H_
+
+namespace talk_base {
+
+void EnsureWinsockInit();
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_WIN32SOCKETINIT_H_
diff --git a/talk/base/win32socketserver.cc b/talk/base/win32socketserver.cc
new file mode 100644
index 0000000..490799c
--- /dev/null
+++ b/talk/base/win32socketserver.cc
@@ -0,0 +1,811 @@
+/*
+ * 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/base/win32socketserver.h"
+#include "talk/base/byteorder.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/winping.h"
+#include "talk/base/win32window.h"
+#include <ws2tcpip.h>  // NOLINT
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// Win32Socket
+///////////////////////////////////////////////////////////////////////////////
+
+// TODO: Move this to a common place where PhysicalSocketServer can
+// share it.
+// Standard MTUs
+static const uint16 PACKET_MAXIMUMS[] = {
+  65535,    // Theoretical maximum, Hyperchannel
+  32000,    // Nothing
+  17914,    // 16Mb IBM Token Ring
+  8166,     // IEEE 802.4
+  // 4464   // IEEE 802.5 (4Mb max)
+  4352,     // FDDI
+  // 2048,  // Wideband Network
+  2002,     // IEEE 802.5 (4Mb recommended)
+  // 1536,  // Expermental Ethernet Networks
+  // 1500,  // Ethernet, Point-to-Point (default)
+  1492,     // IEEE 802.3
+  1006,     // SLIP, ARPANET
+  // 576,   // X.25 Networks
+  // 544,   // DEC IP Portal
+  // 512,   // NETBIOS
+  508,      // IEEE 802/Source-Rt Bridge, ARCNET
+  296,      // Point-to-Point (low delay)
+  68,       // Official minimum
+  0,        // End of list marker
+};
+
+static const uint32 IP_HEADER_SIZE = 20;
+static const uint32 ICMP_HEADER_SIZE = 8;
+
+// TODO: Enable for production builds also? Use FormatMessage?
+#ifdef _DEBUG
+LPCSTR WSAErrorToString(int error, LPCSTR *description_result) {
+  LPCSTR string = "Unspecified";
+  LPCSTR description = "Unspecified description";
+  switch (error) {
+    case ERROR_SUCCESS:
+      string = "SUCCESS";
+      description = "Operation succeeded";
+      break;
+    case WSAEWOULDBLOCK:
+      string = "WSAEWOULDBLOCK";
+      description = "Using a non-blocking socket, will notify later";
+      break;
+    case WSAEACCES:
+      string = "WSAEACCES";
+      description = "Access denied, or sharing violation";
+      break;
+    case WSAEADDRNOTAVAIL:
+      string = "WSAEADDRNOTAVAIL";
+      description = "Address is not valid in this context";
+      break;
+    case WSAENETDOWN:
+      string = "WSAENETDOWN";
+      description = "Network is down";
+      break;
+    case WSAENETUNREACH:
+      string = "WSAENETUNREACH";
+      description = "Network is up, but unreachable";
+      break;
+    case WSAENETRESET:
+      string = "WSANETRESET";
+      description = "Connection has been reset due to keep-alive activity";
+      break;
+    case WSAECONNABORTED:
+      string = "WSAECONNABORTED";
+      description = "Aborted by host";
+      break;
+    case WSAECONNRESET:
+      string = "WSAECONNRESET";
+      description = "Connection reset by host";
+      break;
+    case WSAETIMEDOUT:
+      string = "WSAETIMEDOUT";
+      description = "Timed out, host failed to respond";
+      break;
+    case WSAECONNREFUSED:
+      string = "WSAECONNREFUSED";
+      description = "Host actively refused connection";
+      break;
+    case WSAEHOSTDOWN:
+      string = "WSAEHOSTDOWN";
+      description = "Host is down";
+      break;
+    case WSAEHOSTUNREACH:
+      string = "WSAEHOSTUNREACH";
+      description = "Host is unreachable";
+      break;
+    case WSAHOST_NOT_FOUND:
+      string = "WSAHOST_NOT_FOUND";
+      description = "No such host is known";
+      break;
+  }
+  if (description_result) {
+    *description_result = description;
+  }
+  return string;
+}
+
+void ReportWSAError(LPCSTR context, int error, const SocketAddress& address) {
+  LPCSTR description_string;
+  LPCSTR error_string = WSAErrorToString(error, &description_string);
+  LOG(LS_INFO) << context << " = " << error
+    << " (" << error_string << ":" << description_string << ") ["
+    << address.ToString() << "]";
+}
+#else
+void ReportWSAError(LPCSTR context, int error, const SocketAddress& address) {}
+#endif
+
+/////////////////////////////////////////////////////////////////////////////
+// Win32Socket::EventSink
+/////////////////////////////////////////////////////////////////////////////
+
+#define WM_SOCKETNOTIFY  (WM_USER + 50)
+#define WM_DNSNOTIFY     (WM_USER + 51)
+
+struct Win32Socket::DnsLookup {
+  HANDLE handle;
+  uint16 port;
+  char buffer[MAXGETHOSTSTRUCT];
+};
+
+class Win32Socket::EventSink : public Win32Window {
+ public:
+  explicit EventSink(Win32Socket * parent) : parent_(parent) { }
+
+  void Dispose();
+
+  virtual bool OnMessage(UINT uMsg, WPARAM wParam, LPARAM lParam,
+                         LRESULT& result);
+  virtual void OnFinalMessage(HWND hWnd);
+
+ private:
+  bool OnSocketNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& result);
+  bool OnDnsNotify(WPARAM wParam, LPARAM lParam, LRESULT& result);
+
+  Win32Socket * parent_;
+};
+
+void Win32Socket::EventSink::Dispose() {
+  parent_ = NULL;
+  if (::IsWindow(handle())) {
+    ::DestroyWindow(handle());
+  } else {
+    delete this;
+  }
+}
+
+bool Win32Socket::EventSink::OnMessage(UINT uMsg, WPARAM wParam,
+                                       LPARAM lParam, LRESULT& result) {
+  switch (uMsg) {
+  case WM_SOCKETNOTIFY:
+  case WM_TIMER:
+    return OnSocketNotify(uMsg, wParam, lParam, result);
+  case WM_DNSNOTIFY:
+    return OnDnsNotify(wParam, lParam, result);
+  }
+  return false;
+}
+
+bool Win32Socket::EventSink::OnSocketNotify(UINT uMsg, WPARAM wParam,
+                                            LPARAM lParam, LRESULT& result) {
+  result = 0;
+
+  int wsa_event = WSAGETSELECTEVENT(lParam);
+  int wsa_error = WSAGETSELECTERROR(lParam);
+
+  // Treat connect timeouts as close notifications
+  if (uMsg == WM_TIMER) {
+    wsa_event = FD_CLOSE;
+    wsa_error = WSAETIMEDOUT;
+  }
+
+  if (parent_)
+    parent_->OnSocketNotify(static_cast<SOCKET>(wParam), wsa_event, wsa_error);
+  return true;
+}
+
+bool Win32Socket::EventSink::OnDnsNotify(WPARAM wParam, LPARAM lParam,
+                                         LRESULT& result) {
+  result = 0;
+
+  int error = WSAGETASYNCERROR(lParam);
+  if (parent_)
+    parent_->OnDnsNotify(reinterpret_cast<HANDLE>(wParam), error);
+  return true;
+}
+
+void Win32Socket::EventSink::OnFinalMessage(HWND hWnd) {
+  delete this;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Win32Socket
+/////////////////////////////////////////////////////////////////////////////
+
+Win32Socket::Win32Socket()
+    : socket_(INVALID_SOCKET), error_(0), state_(CS_CLOSED), connect_time_(0),
+      closing_(false), close_error_(0), sink_(NULL), dns_(NULL) {
+}
+
+Win32Socket::~Win32Socket() {
+  Close();
+}
+
+bool Win32Socket::CreateT(int type) {
+  Close();
+  int proto = (SOCK_DGRAM == type) ? IPPROTO_UDP : IPPROTO_TCP;
+  socket_ = ::WSASocket(AF_INET, type, proto, NULL, NULL, 0);
+  if (socket_ == INVALID_SOCKET) {
+    UpdateLastError();
+    return false;
+  }
+  if ((SOCK_DGRAM == type) && !SetAsync(FD_READ | FD_WRITE)) {
+    return false;
+  }
+  return true;
+}
+
+int Win32Socket::Attach(SOCKET s) {
+  ASSERT(socket_ == INVALID_SOCKET);
+  if (socket_ != INVALID_SOCKET)
+    return SOCKET_ERROR;
+
+  ASSERT(s != INVALID_SOCKET);
+  if (s == INVALID_SOCKET)
+    return SOCKET_ERROR;
+
+  socket_ = s;
+  state_ = CS_CONNECTED;
+
+  if (!SetAsync(FD_READ | FD_WRITE | FD_CLOSE))
+    return SOCKET_ERROR;
+
+  return 0;
+}
+
+void Win32Socket::SetTimeout(int ms) {
+  if (sink_)
+    ::SetTimer(sink_->handle(), 1, ms, 0);
+}
+
+SocketAddress Win32Socket::GetLocalAddress() const {
+  sockaddr_in addr;
+  socklen_t addrlen = sizeof(addr);
+  int result = ::getsockname(socket_, reinterpret_cast<sockaddr*>(&addr),
+                             &addrlen);
+  SocketAddress address;
+  if (result >= 0) {
+    ASSERT(addrlen == sizeof(addr));
+    address.FromSockAddr(addr);
+  } else {
+    LOG(LS_WARNING) << "GetLocalAddress: unable to get local addr, socket="
+                    << socket_;
+  }
+  return address;
+}
+
+SocketAddress Win32Socket::GetRemoteAddress() const {
+  sockaddr_in addr;
+  socklen_t addrlen = sizeof(addr);
+  int result = ::getpeername(socket_, reinterpret_cast<sockaddr*>(&addr),
+                             &addrlen);
+  ASSERT(addrlen == sizeof(addr));
+  SocketAddress address;
+  if (result >= 0) {
+    ASSERT(addrlen == sizeof(addr));
+    address.FromSockAddr(addr);
+  } else {
+    LOG(LS_WARNING) << "GetRemoteAddress: unable to get remote addr, socket="
+                    << socket_;
+  }
+  return address;
+}
+
+int Win32Socket::Bind(const SocketAddress& addr) {
+  ASSERT(socket_ != INVALID_SOCKET);
+  if (socket_ == INVALID_SOCKET)
+    return SOCKET_ERROR;
+
+  sockaddr_in saddr;
+  addr.ToSockAddr(&saddr);
+  int err = ::bind(socket_, reinterpret_cast<sockaddr*>(&saddr), sizeof(saddr));
+  UpdateLastError();
+  return err;
+}
+
+int Win32Socket::Connect(const SocketAddress& addr) {
+  if ((socket_ == INVALID_SOCKET) && !CreateT(SOCK_STREAM))
+    return SOCKET_ERROR;
+
+  if (!sink_ && !SetAsync(FD_READ | FD_WRITE | FD_CONNECT | FD_CLOSE))
+    return SOCKET_ERROR;
+
+  // If we have an IP address, connect now.
+  if (!addr.IsUnresolved()) {
+    return DoConnect(addr);
+  }
+
+  LOG_F(LS_INFO) << "async dns lookup (" << addr.IPAsString() << ")";
+  DnsLookup * dns = new DnsLookup;
+  dns->handle = WSAAsyncGetHostByName(sink_->handle(), WM_DNSNOTIFY,
+        addr.IPAsString().c_str(), dns->buffer, sizeof(dns->buffer));
+
+  if (!dns->handle) {
+    LOG_F(LS_ERROR) << "WSAAsyncGetHostByName error: " << WSAGetLastError();
+    delete dns;
+    UpdateLastError();
+    Close();
+    return SOCKET_ERROR;
+  }
+
+  dns->port = addr.port();
+  dns_ = dns;
+  state_ = CS_CONNECTING;
+  return 0;
+}
+
+int Win32Socket::DoConnect(const SocketAddress& addr) {
+  sockaddr_in saddr;
+  addr.ToSockAddr(&saddr);
+  connect_time_ = Time();
+  int result = connect(socket_, reinterpret_cast<SOCKADDR*>(&saddr),
+                       sizeof(saddr));
+  if (result != SOCKET_ERROR) {
+    state_ = CS_CONNECTED;
+  } else {
+    int code = WSAGetLastError();
+    if (code == WSAEWOULDBLOCK) {
+      state_ = CS_CONNECTING;
+    } else {
+      ReportWSAError("WSAAsync:connect", code, addr);
+      error_ = code;
+      Close();
+      return SOCKET_ERROR;
+    }
+  }
+  addr_ = addr;
+
+  return 0;
+}
+
+int Win32Socket::GetError() const {
+  return error_;
+}
+
+void Win32Socket::SetError(int error) {
+  error_ = error;
+}
+
+Socket::ConnState Win32Socket::GetState() const {
+  return state_;
+}
+
+int Win32Socket::GetOption(Option opt, int* value) {
+  int slevel;
+  int sopt;
+  if (TranslateOption(opt, &slevel, &sopt) == -1)
+    return -1;
+
+  char* p = reinterpret_cast<char*>(value);
+  int optlen = sizeof(value);
+  return ::getsockopt(socket_, slevel, sopt, p, &optlen);
+}
+
+int Win32Socket::SetOption(Option opt, int value) {
+  int slevel;
+  int sopt;
+  if (TranslateOption(opt, &slevel, &sopt) == -1)
+    return -1;
+
+  const char* p = reinterpret_cast<const char*>(&value);
+  return ::setsockopt(socket_, slevel, sopt, p, sizeof(value));
+}
+
+int Win32Socket::Send(const void *pv, size_t cb) {
+  int sent = ::send(socket_, reinterpret_cast<const char*>(pv), cb, 0);
+  UpdateLastError();
+  return sent;
+}
+
+int Win32Socket::SendTo(const void *pv, size_t cb,
+                        const SocketAddress& addr) {
+  sockaddr_in saddr;
+  addr.ToSockAddr(&saddr);
+  int sent = ::sendto(socket_, reinterpret_cast<const char*>(pv), cb, 0,
+                      reinterpret_cast<sockaddr*>(&saddr), sizeof(saddr));
+  UpdateLastError();
+  return sent;
+}
+
+int Win32Socket::Recv(void *pv, size_t cb) {
+  int received = ::recv(socket_, static_cast<char*>(pv), cb, 0);
+  UpdateLastError();
+  if (closing_ && received <= static_cast<int>(cb))
+    PostClosed();
+  return received;
+}
+
+int Win32Socket::RecvFrom(void *pv, size_t cb,
+                          SocketAddress *paddr) {
+  sockaddr_in saddr;
+  socklen_t cbAddr = sizeof(saddr);
+  int received = ::recvfrom(socket_, static_cast<char*>(pv), cb, 0,
+                            reinterpret_cast<sockaddr*>(&saddr), &cbAddr);
+  UpdateLastError();
+  if (received != SOCKET_ERROR)
+    paddr->FromSockAddr(saddr);
+  if (closing_ && received <= static_cast<int>(cb))
+    PostClosed();
+  return received;
+}
+
+int Win32Socket::Listen(int backlog) {
+  int err = ::listen(socket_, backlog);
+  if (!SetAsync(FD_ACCEPT))
+    return SOCKET_ERROR;
+
+  UpdateLastError();
+  if (err == 0)
+    state_ = CS_CONNECTING;
+  return err;
+}
+
+Win32Socket* Win32Socket::Accept(SocketAddress *paddr) {
+  sockaddr_in saddr;
+  socklen_t cbAddr = sizeof(saddr);
+  SOCKET s = ::accept(socket_, reinterpret_cast<sockaddr*>(&saddr), &cbAddr);
+  UpdateLastError();
+  if (s == INVALID_SOCKET)
+    return NULL;
+  if (paddr)
+    paddr->FromSockAddr(saddr);
+  Win32Socket* socket = new Win32Socket;
+  if (0 == socket->Attach(s))
+    return socket;
+  delete socket;
+  return NULL;
+}
+
+int Win32Socket::Close() {
+  int err = 0;
+  if (socket_ != INVALID_SOCKET) {
+    err = ::closesocket(socket_);
+    socket_ = INVALID_SOCKET;
+    closing_ = false;
+    close_error_ = 0;
+    UpdateLastError();
+  }
+  if (dns_) {
+    WSACancelAsyncRequest(dns_->handle);
+    delete dns_;
+    dns_ = NULL;
+  }
+  if (sink_) {
+    sink_->Dispose();
+    sink_ = NULL;
+  }
+  addr_.Clear();
+  state_ = CS_CLOSED;
+  return err;
+}
+
+int Win32Socket::EstimateMTU(uint16* mtu) {
+  SocketAddress addr = GetRemoteAddress();
+  if (addr.IsAny()) {
+    error_ = ENOTCONN;
+    return -1;
+  }
+
+  WinPing ping;
+  if (!ping.IsValid()) {
+    error_ = EINVAL;  // can't think of a better error ID
+    return -1;
+  }
+
+  for (int level = 0; PACKET_MAXIMUMS[level + 1] > 0; ++level) {
+    int32 size = PACKET_MAXIMUMS[level] - IP_HEADER_SIZE - ICMP_HEADER_SIZE;
+    WinPing::PingResult result = ping.Ping(addr.ip(), size, 0, 1, false);
+    if (result == WinPing::PING_FAIL) {
+      error_ = EINVAL;  // can't think of a better error ID
+      return -1;
+    }
+    if (result != WinPing::PING_TOO_LARGE) {
+      *mtu = PACKET_MAXIMUMS[level];
+      return 0;
+    }
+  }
+
+  ASSERT(false);
+  return 0;
+}
+
+bool Win32Socket::SetAsync(int events) {
+  ASSERT(NULL == sink_);
+
+  // Create window
+  sink_ = new EventSink(this);
+  sink_->Create(NULL, L"EventSink", 0, 0, 0, 0, 10, 10);
+
+  // start the async select
+  if (WSAAsyncSelect(socket_, sink_->handle(), WM_SOCKETNOTIFY, events)
+      == SOCKET_ERROR) {
+    UpdateLastError();
+    Close();
+    return false;
+  }
+
+  return true;
+}
+
+bool Win32Socket::HandleClosed(int close_error) {
+  // WM_CLOSE will be received before all data has been read, so we need to
+  // hold on to it until the read buffer has been drained.
+  char ch;
+  closing_ = true;
+  close_error_ = close_error;
+  return (::recv(socket_, &ch, 1, MSG_PEEK) <= 0);
+}
+
+void Win32Socket::PostClosed() {
+  // If we see that the buffer is indeed drained, then send the close.
+  closing_ = false;
+  ::PostMessage(sink_->handle(), WM_SOCKETNOTIFY,
+                socket_, WSAMAKESELECTREPLY(FD_CLOSE, close_error_));
+}
+
+void Win32Socket::UpdateLastError() {
+  error_ = WSAGetLastError();
+}
+
+int Win32Socket::TranslateOption(Option opt, int* slevel, int* sopt) {
+  switch (opt) {
+    case OPT_DONTFRAGMENT:
+      *slevel = IPPROTO_IP;
+      *sopt = IP_DONTFRAGMENT;
+      break;
+    case OPT_RCVBUF:
+      *slevel = SOL_SOCKET;
+      *sopt = SO_RCVBUF;
+      break;
+    case OPT_SNDBUF:
+      *slevel = SOL_SOCKET;
+      *sopt = SO_SNDBUF;
+      break;
+    case OPT_NODELAY:
+      *slevel = IPPROTO_TCP;
+      *sopt = TCP_NODELAY;
+      break;
+    default:
+      ASSERT(false);
+      return -1;
+  }
+  return 0;
+}
+
+void Win32Socket::OnSocketNotify(SOCKET socket, int event, int error) {
+  // Ignore events if we're already closed.
+  if (socket != socket_)
+    return;
+
+  error_ = error;
+  switch (event) {
+    case FD_CONNECT:
+      if (error != ERROR_SUCCESS) {
+        ReportWSAError("WSAAsync:connect notify", error, addr_);
+#ifdef _DEBUG
+        int32 duration = TimeSince(connect_time_);
+        LOG(LS_INFO) << "WSAAsync:connect error (" << duration
+                     << " ms), faking close";
+#endif
+        state_ = CS_CLOSED;
+        // If you get an error connecting, close doesn't really do anything
+        // and it certainly doesn't send back any close notification, but
+        // we really only maintain a few states, so it is easiest to get
+        // back into a known state by pretending that a close happened, even
+        // though the connect event never did occur.
+        SignalCloseEvent(this, error);
+      } else {
+#ifdef _DEBUG
+        int32 duration = TimeSince(connect_time_);
+        LOG(LS_INFO) << "WSAAsync:connect (" << duration << " ms)";
+#endif
+        state_ = CS_CONNECTED;
+        SignalConnectEvent(this);
+      }
+      break;
+
+    case FD_ACCEPT:
+    case FD_READ:
+      if (error != ERROR_SUCCESS) {
+        ReportWSAError("WSAAsync:read notify", error, addr_);
+      } else {
+        SignalReadEvent(this);
+      }
+      break;
+
+    case FD_WRITE:
+      if (error != ERROR_SUCCESS) {
+        ReportWSAError("WSAAsync:write notify", error, addr_);
+      } else {
+        SignalWriteEvent(this);
+      }
+      break;
+
+    case FD_CLOSE:
+      if (HandleClosed(error)) {
+        ReportWSAError("WSAAsync:close notify", error, addr_);
+        state_ = CS_CLOSED;
+        SignalCloseEvent(this, error);
+      }
+      break;
+  }
+}
+
+void Win32Socket::OnDnsNotify(HANDLE task, int error) {
+  if (!dns_ || dns_->handle != task)
+    return;
+
+  uint32 ip = 0;
+  if (error == 0) {
+    hostent* pHost = reinterpret_cast<hostent*>(dns_->buffer);
+    uint32 net_ip = *reinterpret_cast<uint32*>(pHost->h_addr_list[0]);
+    ip = NetworkToHost32(net_ip);
+  }
+
+  LOG_F(LS_INFO) << "(" << SocketAddress::IPToString(ip)
+                 << ", " << error << ")";
+
+  if (error == 0) {
+    SocketAddress address(ip, dns_->port);
+    error = DoConnect(address);
+  } else {
+    Close();
+  }
+
+  if (error) {
+    error_ = error;
+    SignalCloseEvent(this, error_);
+  } else {
+    delete dns_;
+    dns_ = NULL;
+  }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Win32SocketServer
+// Provides cricket base services on top of a win32 gui thread
+///////////////////////////////////////////////////////////////////////////////
+
+static UINT s_wm_wakeup_id = 0;
+const TCHAR Win32SocketServer::kWindowName[] = L"libjingle Message Window";
+
+Win32SocketServer::Win32SocketServer(MessageQueue *message_queue)
+    : message_queue_(message_queue), wnd_(this), posted_(false) {
+  if (s_wm_wakeup_id == 0)
+    s_wm_wakeup_id = RegisterWindowMessage(L"WM_WAKEUP");
+  if (!wnd_.Create(NULL, kWindowName, 0, 0, 0, 0, 0, 0)) {
+    LOG_GLE(LS_ERROR) << "Failed to create message window.";
+  }
+}
+
+Win32SocketServer::~Win32SocketServer() {
+  if (wnd_.handle() != NULL) {
+    KillTimer(wnd_.handle(), 1);
+    wnd_.Destroy();
+  }
+}
+
+Socket* Win32SocketServer::CreateSocket(int type) {
+  return CreateAsyncSocket(type);
+}
+
+AsyncSocket* Win32SocketServer::CreateAsyncSocket(int type) {
+  Win32Socket* socket = new Win32Socket;
+  if (socket->CreateT(type)) {
+    return socket;
+  }
+  delete socket;
+  return NULL;
+}
+
+void Win32SocketServer::SetMessageQueue(MessageQueue* queue) {
+  message_queue_ = queue;
+}
+
+bool Win32SocketServer::Wait(int cms, bool process_io) {
+  BOOL b;
+  if (process_io) {
+    // Spin the Win32 message pump at least once, and as long as requested.
+    // This is the Thread::ProcessMessages case.
+    uint32 start = Time();
+    MSG msg;
+    do {
+      SetTimer(wnd_.handle(), 0, cms, NULL);
+      b = GetMessage(&msg, NULL, 0, 0);
+      if (b) {
+        TranslateMessage(&msg);
+        DispatchMessage(&msg);
+      }
+      KillTimer(wnd_.handle(), 0);
+    } while (b && TimeSince(start) < cms);
+  } else if (cms != 0) {
+    // Sit and wait forever for a WakeUp. This is the Thread::Send case.
+    ASSERT(cms == -1);
+    MSG msg;
+    b = GetMessage(&msg, NULL, s_wm_wakeup_id, s_wm_wakeup_id);
+    {
+      CritScope scope(&cs_);
+      posted_ = false;
+    }
+  } else {
+    // No-op (cms == 0 && !process_io). This is the Pump case.
+    b = TRUE;
+  }
+  return (b != FALSE);
+}
+
+void Win32SocketServer::WakeUp() {
+  if (wnd_.handle()) {
+    // Set the "message pending" flag, if not already set.
+    {
+      CritScope scope(&cs_);
+      if (posted_)
+        return;
+      posted_ = true;
+    }
+
+    PostMessage(wnd_.handle(), s_wm_wakeup_id, 0, 0);
+  }
+}
+
+void Win32SocketServer::Pump() {
+  // Clear the "message pending" flag.
+  {
+    CritScope scope(&cs_);
+    posted_ = false;
+  }
+
+  // Dispatch all the messages that are currently in our queue. If new messages
+  // are posted during the dispatch, they will be handled in the next Pump.
+  // We use max(1, ...) to make sure we try to dispatch at least once, since
+  // this allow us to process "sent" messages, not included in the size() count.
+  Message msg;
+  for (size_t max_messages_to_process = _max<size_t>(1, message_queue_->size());
+       max_messages_to_process > 0 && message_queue_->Get(&msg, 0, false);
+       --max_messages_to_process) {
+    message_queue_->Dispatch(&msg);
+  }
+
+  // Anything remaining?
+  int delay = message_queue_->GetDelay();
+  if (delay == -1) {
+    KillTimer(wnd_.handle(), 1);
+  } else {
+    SetTimer(wnd_.handle(), 1, delay, NULL);
+  }
+}
+
+bool Win32SocketServer::MessageWindow::OnMessage(UINT wm, WPARAM wp,
+                                                 LPARAM lp, LRESULT& lr) {
+  bool handled = false;
+  if (wm == s_wm_wakeup_id || (wm == WM_TIMER && wp == 1)) {
+    ss_->Pump();
+    lr = 0;
+    handled = true;
+  }
+  return handled;
+}
+
+}  // namespace talk_base
diff --git a/talk/base/win32socketserver.h b/talk/base/win32socketserver.h
new file mode 100644
index 0000000..f14a197
--- /dev/null
+++ b/talk/base/win32socketserver.h
@@ -0,0 +1,174 @@
+/*
+ * 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_BASE_WIN32SOCKETSERVER_H_
+#define TALK_BASE_WIN32SOCKETSERVER_H_
+
+#ifdef WIN32
+#include "talk/base/asyncsocket.h"
+#include "talk/base/criticalsection.h"
+#include "talk/base/messagequeue.h"
+#include "talk/base/socketserver.h"
+#include "talk/base/socketfactory.h"
+#include "talk/base/socket.h"
+#include "talk/base/thread.h"
+#include "talk/base/win32window.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// Win32Socket
+///////////////////////////////////////////////////////////////////////////////
+
+class Win32Socket : public AsyncSocket {
+ public:
+  Win32Socket();
+  virtual ~Win32Socket();
+
+  bool CreateT(int type);
+
+  int Attach(SOCKET s);
+  void SetTimeout(int ms);
+
+  // AsyncSocket Interface
+  virtual SocketAddress GetLocalAddress() const;
+  virtual SocketAddress GetRemoteAddress() const;
+  virtual int Bind(const SocketAddress& addr);
+  virtual int Connect(const SocketAddress& addr);
+  virtual int Send(const void *pv, size_t cb);
+  virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr);
+  virtual int Recv(void *pv, size_t cb);
+  virtual int RecvFrom(void *pv, size_t cb, SocketAddress *paddr);
+  virtual int Listen(int backlog);
+  virtual Win32Socket *Accept(SocketAddress *paddr);
+  virtual int Close();
+  virtual int GetError() const;
+  virtual void SetError(int error);
+  virtual ConnState GetState() const;
+  virtual int EstimateMTU(uint16* mtu);
+  virtual int GetOption(Option opt, int* value);
+  virtual int SetOption(Option opt, int value);
+
+ private:
+  bool SetAsync(int events);
+  int DoConnect(const SocketAddress& addr);
+  bool HandleClosed(int close_error);
+  void PostClosed();
+  void UpdateLastError();
+  static int TranslateOption(Option opt, int* slevel, int* sopt);
+
+  void OnSocketNotify(SOCKET socket, int event, int error);
+  void OnDnsNotify(HANDLE task, int error);
+
+  SOCKET socket_;
+  int error_;
+  ConnState state_;
+  SocketAddress addr_;         // address that we connected to (see DoConnect)
+  uint32 connect_time_;
+  bool closing_;
+  int close_error_;
+
+  class EventSink;
+  friend class EventSink;
+  EventSink * sink_;
+
+  struct DnsLookup;
+  DnsLookup * dns_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Win32SocketServer
+///////////////////////////////////////////////////////////////////////////////
+
+class Win32SocketServer : public SocketServer {
+ public:
+  explicit Win32SocketServer(MessageQueue *message_queue);
+  virtual ~Win32SocketServer();
+
+  // SocketServer Interface
+  virtual Socket* CreateSocket(int type);
+  virtual AsyncSocket* CreateAsyncSocket(int type);
+  virtual void SetMessageQueue(MessageQueue* queue);
+  virtual bool Wait(int cms, bool process_io);
+  virtual void WakeUp();
+
+  void Pump();
+
+  HWND handle() { return wnd_.handle(); }
+
+ private:
+  class MessageWindow : public Win32Window {
+   public:
+    explicit MessageWindow(Win32SocketServer* ss) : ss_(ss) {}
+   private:
+    virtual bool OnMessage(UINT msg, WPARAM wp, LPARAM lp, LRESULT& result);
+    Win32SocketServer* ss_;
+  };
+
+  static const TCHAR kWindowName[];
+  MessageQueue *message_queue_;
+  MessageWindow wnd_;
+  CriticalSection cs_;
+  bool posted_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Win32Thread. Automatically pumps Windows messages.
+///////////////////////////////////////////////////////////////////////////////
+
+class Win32Thread : public Thread {
+ public:
+  Win32Thread() : ss_(this), id_(0) {
+    set_socketserver(&ss_);
+  }
+  virtual ~Win32Thread() {
+    set_socketserver(NULL);
+  }
+  virtual void Run() {
+    MSG msg;
+    id_ = GetCurrentThreadId();
+    while (GetMessage(&msg, NULL, 0, 0)) {
+      TranslateMessage(&msg);
+      DispatchMessage(&msg);
+    }
+    id_ = 0;
+  }
+  virtual void Quit() {
+    PostThreadMessage(id_, WM_QUIT, 0, 0);
+  }
+ private:
+  Win32SocketServer ss_;
+  DWORD id_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif  // WIN32
+
+#endif  // TALK_BASE_WIN32SOCKETSERVER_H_
diff --git a/talk/base/win32window.cc b/talk/base/win32window.cc
new file mode 100644
index 0000000..0e7761f
--- /dev/null
+++ b/talk/base/win32window.cc
@@ -0,0 +1,134 @@
+/*
+ * 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/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/win32window.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// Win32Window
+///////////////////////////////////////////////////////////////////////////////
+
+static const wchar_t kWindowBaseClassName[] = L"WindowBaseClass";
+HINSTANCE Win32Window::instance_ = GetModuleHandle(NULL);
+ATOM Win32Window::window_class_ = 0;
+
+Win32Window::Win32Window() : wnd_(NULL) {
+}
+
+Win32Window::~Win32Window() {
+  ASSERT(NULL == wnd_);
+}
+
+bool Win32Window::Create(HWND parent, const wchar_t* title, DWORD style,
+                         DWORD exstyle, int x, int y, int cx, int cy) {
+  if (wnd_) {
+    // Window already exists.
+    return false;
+  }
+
+  if (!window_class_) {
+    // Class not registered, register it.
+    WNDCLASSEX wcex;
+    memset(&wcex, 0, sizeof(wcex));
+    wcex.cbSize = sizeof(wcex);
+    wcex.hInstance = instance_;
+    wcex.lpfnWndProc = &Win32Window::WndProc;
+    wcex.lpszClassName = kWindowBaseClassName;
+    window_class_ = ::RegisterClassEx(&wcex);
+    if (!window_class_) {
+      LOG_GLE(LS_ERROR) << "RegisterClassEx failed";
+      return false;
+    }
+  }
+  wnd_ = ::CreateWindowEx(exstyle, kWindowBaseClassName, title, style,
+                          x, y, cx, cy, parent, NULL, instance_, this);
+  return (NULL != wnd_);
+}
+
+void Win32Window::Destroy() {
+  VERIFY(::DestroyWindow(wnd_) != FALSE);
+}
+
+void Win32Window::SetInstance(HINSTANCE instance) {
+  instance_ = instance;
+}
+
+void Win32Window::Shutdown() {
+  if (window_class_) {
+    ::UnregisterClass(MAKEINTATOM(window_class_), instance_);
+    window_class_ = 0;
+  }
+}
+
+bool Win32Window::OnMessage(UINT uMsg, WPARAM wParam, LPARAM lParam,
+                            LRESULT& result) {
+  switch (uMsg) {
+  case WM_CLOSE:
+    if (!OnClose()) {
+      result = 0;
+      return true;
+    }
+    break;
+  }
+  return false;
+}
+
+LRESULT Win32Window::WndProc(HWND hwnd, UINT uMsg,
+                             WPARAM wParam, LPARAM lParam) {
+  Win32Window* that = reinterpret_cast<Win32Window*>(
+      ::GetWindowLongPtr(hwnd, GWL_USERDATA));
+  if (!that && (WM_CREATE == uMsg)) {
+    CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(lParam);
+    that = static_cast<Win32Window*>(cs->lpCreateParams);
+    that->wnd_ = hwnd;
+    ::SetWindowLongPtr(hwnd, GWL_USERDATA, reinterpret_cast<LONG_PTR>(that));
+  }
+  if (that) {
+    LRESULT result;
+    bool handled = that->OnMessage(uMsg, wParam, lParam, result);
+    if (WM_DESTROY == uMsg) {
+      for (HWND child = ::GetWindow(hwnd, GW_CHILD); child;
+           child = ::GetWindow(child, GW_HWNDNEXT)) {
+        LOG(LS_INFO) << "Child window: " << static_cast<void*>(child);
+      }
+    }
+    if (WM_NCDESTROY == uMsg) {
+      ::SetWindowLongPtr(hwnd, GWL_USERDATA, NULL);
+      that->wnd_ = NULL;
+      that->OnDestroyed();
+    }
+    if (handled) {
+      return result;
+    }
+  }
+  return ::DefWindowProc(hwnd, uMsg, wParam, lParam);
+}
+
+}  // namespace talk_base
diff --git a/talk/base/win32window.h b/talk/base/win32window.h
new file mode 100644
index 0000000..66a56ce
--- /dev/null
+++ b/talk/base/win32window.h
@@ -0,0 +1,79 @@
+/*
+ * 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_BASE_WIN32WINDOW_H_
+#define TALK_BASE_WIN32WINDOW_H_
+
+#ifdef WIN32
+
+#include "talk/base/win32.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// Win32Window
+///////////////////////////////////////////////////////////////////////////////
+
+class Win32Window {
+ public:
+  Win32Window();
+  virtual ~Win32Window();
+
+  HWND handle() const { return wnd_; }
+
+  bool Create(HWND parent, const wchar_t* title, DWORD style, DWORD exstyle,
+              int x, int y, int cx, int cy);
+  void Destroy();
+
+  // Call this first if you are running inside a DLL.
+  static void SetInstance(HINSTANCE instance);
+  // Call this when your DLL unloads.
+  static void Shutdown();
+
+ protected:
+  virtual bool OnMessage(UINT uMsg, WPARAM wParam, LPARAM lParam,
+                         LRESULT& result);
+
+  virtual bool OnClose() { return true; }
+  virtual void OnDestroyed() { }
+
+ private:
+  static LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam,
+                                  LPARAM lParam);
+
+  HWND wnd_;
+  static HINSTANCE instance_;
+  static ATOM window_class_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif  // WIN32
+
+#endif  // TALK_BASE_WIN32WINDOW_H_
diff --git a/talk/base/winfirewall.cc b/talk/base/winfirewall.cc
new file mode 100644
index 0000000..e87ee5a
--- /dev/null
+++ b/talk/base/winfirewall.cc
@@ -0,0 +1,172 @@
+/*
+ * 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/base/winfirewall.h"
+
+#include "talk/base/win32.h"
+
+#include <comdef.h>
+#include <netfw.h>
+
+#define RELEASE(lpUnk) do { \
+  if ((lpUnk) != NULL) { \
+    (lpUnk)->Release(); \
+    (lpUnk) = NULL; \
+  } \
+} while (0)
+
+namespace talk_base {
+
+//////////////////////////////////////////////////////////////////////
+// WinFirewall
+//////////////////////////////////////////////////////////////////////
+
+WinFirewall::WinFirewall() : mgr_(NULL), policy_(NULL), profile_(NULL) {
+}
+
+WinFirewall::~WinFirewall() {
+  Shutdown();
+}
+
+bool WinFirewall::Initialize(HRESULT* result) {
+  if (mgr_) {
+    if (result) {
+      *result = S_OK;
+    }
+    return true;
+  }
+
+  HRESULT hr = CoCreateInstance(__uuidof(NetFwMgr),
+                                0, CLSCTX_INPROC_SERVER,
+                                __uuidof(INetFwMgr),
+                                reinterpret_cast<void **>(&mgr_));
+  if (SUCCEEDED(hr) && (mgr_ != NULL))
+    hr = mgr_->get_LocalPolicy(&policy_);
+  if (SUCCEEDED(hr) && (policy_ != NULL))
+    hr = policy_->get_CurrentProfile(&profile_);
+
+  if (result)
+    *result = hr;
+  return SUCCEEDED(hr) && (profile_ != NULL);
+}
+
+void WinFirewall::Shutdown() {
+  RELEASE(profile_);
+  RELEASE(policy_);
+  RELEASE(mgr_);
+}
+
+bool WinFirewall::Enabled() const {
+  if (!profile_)
+    return false;
+
+  VARIANT_BOOL fwEnabled = VARIANT_FALSE;
+  profile_->get_FirewallEnabled(&fwEnabled);
+  return (fwEnabled != VARIANT_FALSE);
+}
+
+bool WinFirewall::QueryAuthorized(const char* filename, bool* authorized)
+    const {
+  return QueryAuthorizedW(ToUtf16(filename).c_str(), authorized);
+}
+
+bool WinFirewall::QueryAuthorizedW(const wchar_t* filename, bool* authorized)
+    const {
+  *authorized = false;
+  bool success = false;
+
+  if (!profile_)
+    return false;
+
+  _bstr_t bfilename = filename;
+
+  INetFwAuthorizedApplications* apps = NULL;
+  HRESULT hr = profile_->get_AuthorizedApplications(&apps);
+  if (SUCCEEDED(hr) && (apps != NULL)) {
+    INetFwAuthorizedApplication* app = NULL;
+    hr = apps->Item(bfilename, &app);
+    if (SUCCEEDED(hr) && (app != NULL)) {
+      VARIANT_BOOL fwEnabled = VARIANT_FALSE;
+      hr = app->get_Enabled(&fwEnabled);
+      app->Release();
+
+      if (SUCCEEDED(hr)) {
+        success = true;
+        *authorized = (fwEnabled != VARIANT_FALSE);
+      }
+    } else if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) {
+      // No entry in list of authorized apps
+      success = true;
+    } else {
+      // Unexpected error
+    }
+    apps->Release();
+  }
+
+  return success;
+}
+
+bool WinFirewall::AddApplication(const char* filename,
+                                 const char* friendly_name,
+                                 bool authorized,
+                                 HRESULT* result) {
+  return AddApplicationW(ToUtf16(filename).c_str(),
+      ToUtf16(friendly_name).c_str(), authorized, result);
+}
+
+bool WinFirewall::AddApplicationW(const wchar_t* filename,
+                                  const wchar_t* friendly_name,
+                                  bool authorized,
+                                  HRESULT* result) {
+  INetFwAuthorizedApplications* apps = NULL;
+  HRESULT hr = profile_->get_AuthorizedApplications(&apps);
+  if (SUCCEEDED(hr) && (apps != NULL)) {
+    INetFwAuthorizedApplication* app = NULL;
+    hr = CoCreateInstance(__uuidof(NetFwAuthorizedApplication),
+                          0, CLSCTX_INPROC_SERVER,
+                          __uuidof(INetFwAuthorizedApplication),
+                          reinterpret_cast<void **>(&app));
+    if (SUCCEEDED(hr) && (app != NULL)) {
+      _bstr_t bstr = filename;
+      hr = app->put_ProcessImageFileName(bstr);
+      bstr = friendly_name;
+      if (SUCCEEDED(hr))
+        hr = app->put_Name(bstr);
+      if (SUCCEEDED(hr))
+        hr = app->put_Enabled(authorized ? VARIANT_TRUE : VARIANT_FALSE);
+      if (SUCCEEDED(hr))
+        hr = apps->Add(app);
+      app->Release();
+    }
+    apps->Release();
+  }
+  if (result)
+    *result = hr;
+  return SUCCEEDED(hr);
+}
+
+}  // namespace talk_base
diff --git a/talk/base/winfirewall.h b/talk/base/winfirewall.h
new file mode 100644
index 0000000..11d687e
--- /dev/null
+++ b/talk/base/winfirewall.h
@@ -0,0 +1,73 @@
+/*
+ * 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_BASE_WINFIREWALL_H_
+#define TALK_BASE_WINFIREWALL_H_
+
+#ifndef _HRESULT_DEFINED
+#define _HRESULT_DEFINED
+typedef long HRESULT;  // Can't forward declare typedef, but don't need all win
+#endif // !_HRESULT_DEFINED
+
+struct INetFwMgr;
+struct INetFwPolicy;
+struct INetFwProfile;
+
+namespace talk_base {
+
+//////////////////////////////////////////////////////////////////////
+// WinFirewall
+//////////////////////////////////////////////////////////////////////
+
+class WinFirewall {
+ public:
+  WinFirewall();
+  ~WinFirewall();
+
+  bool Initialize(HRESULT* result);
+  void Shutdown();
+
+  bool Enabled() const;
+  bool QueryAuthorized(const char* filename, bool* authorized) const;
+  bool QueryAuthorizedW(const wchar_t* filename, bool* authorized) const;
+
+  bool AddApplication(const char* filename, const char* friendly_name,
+                      bool authorized, HRESULT* result);
+  bool AddApplicationW(const wchar_t* filename, const wchar_t* friendly_name,
+                       bool authorized, HRESULT* result);
+
+ private:
+  INetFwMgr* mgr_;
+  INetFwPolicy* policy_;
+  INetFwProfile* profile_;
+};
+
+//////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_WINFIREWALL_H_
diff --git a/talk/base/winping.cc b/talk/base/winping.cc
new file mode 100644
index 0000000..1802e03
--- /dev/null
+++ b/talk/base/winping.cc
@@ -0,0 +1,318 @@
+/*
+ * 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/base/byteorder.h"
+#include "talk/base/common.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/winping.h"
+#include "talk/base/logging.h"
+#include <cassert>
+
+namespace talk_base {
+
+//////////////////////////////////////////////////////////////////////
+// Found in IPExport.h
+//////////////////////////////////////////////////////////////////////
+
+typedef struct icmp_echo_reply {
+    ULONG   Address;            // Replying address
+    ULONG   Status;             // Reply IP_STATUS
+    ULONG   RoundTripTime;      // RTT in milliseconds
+    USHORT  DataSize;           // Reply data size in bytes
+    USHORT  Reserved;           // Reserved for system use
+    PVOID   Data;               // Pointer to the reply data
+    struct ip_option_information Options; // Reply options
+} ICMP_ECHO_REPLY, * PICMP_ECHO_REPLY;
+
+//
+// IP_STATUS codes returned from IP APIs
+//
+
+#define IP_STATUS_BASE              11000
+
+#define IP_SUCCESS                  0
+#define IP_BUF_TOO_SMALL            (IP_STATUS_BASE + 1)
+#define IP_DEST_NET_UNREACHABLE     (IP_STATUS_BASE + 2)
+#define IP_DEST_HOST_UNREACHABLE    (IP_STATUS_BASE + 3)
+#define IP_DEST_PROT_UNREACHABLE    (IP_STATUS_BASE + 4)
+#define IP_DEST_PORT_UNREACHABLE    (IP_STATUS_BASE + 5)
+#define IP_NO_RESOURCES             (IP_STATUS_BASE + 6)
+#define IP_BAD_OPTION               (IP_STATUS_BASE + 7)
+#define IP_HW_ERROR                 (IP_STATUS_BASE + 8)
+#define IP_PACKET_TOO_BIG           (IP_STATUS_BASE + 9)
+#define IP_REQ_TIMED_OUT            (IP_STATUS_BASE + 10)
+#define IP_BAD_REQ                  (IP_STATUS_BASE + 11)
+#define IP_BAD_ROUTE                (IP_STATUS_BASE + 12)
+#define IP_TTL_EXPIRED_TRANSIT      (IP_STATUS_BASE + 13)
+#define IP_TTL_EXPIRED_REASSEM      (IP_STATUS_BASE + 14)
+#define IP_PARAM_PROBLEM            (IP_STATUS_BASE + 15)
+#define IP_SOURCE_QUENCH            (IP_STATUS_BASE + 16)
+#define IP_OPTION_TOO_BIG           (IP_STATUS_BASE + 17)
+#define IP_BAD_DESTINATION          (IP_STATUS_BASE + 18)
+
+#define IP_ADDR_DELETED             (IP_STATUS_BASE + 19)
+#define IP_SPEC_MTU_CHANGE          (IP_STATUS_BASE + 20)
+#define IP_MTU_CHANGE               (IP_STATUS_BASE + 21)
+#define IP_UNLOAD                   (IP_STATUS_BASE + 22)
+#define IP_ADDR_ADDED               (IP_STATUS_BASE + 23)
+#define IP_MEDIA_CONNECT            (IP_STATUS_BASE + 24)
+#define IP_MEDIA_DISCONNECT         (IP_STATUS_BASE + 25)
+#define IP_BIND_ADAPTER             (IP_STATUS_BASE + 26)
+#define IP_UNBIND_ADAPTER           (IP_STATUS_BASE + 27)
+#define IP_DEVICE_DOES_NOT_EXIST    (IP_STATUS_BASE + 28)
+#define IP_DUPLICATE_ADDRESS        (IP_STATUS_BASE + 29)
+#define IP_INTERFACE_METRIC_CHANGE  (IP_STATUS_BASE + 30)
+#define IP_RECONFIG_SECFLTR         (IP_STATUS_BASE + 31)
+#define IP_NEGOTIATING_IPSEC        (IP_STATUS_BASE + 32)
+#define IP_INTERFACE_WOL_CAPABILITY_CHANGE  (IP_STATUS_BASE + 33)
+#define IP_DUPLICATE_IPADD          (IP_STATUS_BASE + 34)
+
+#define IP_GENERAL_FAILURE          (IP_STATUS_BASE + 50)
+#define MAX_IP_STATUS               IP_GENERAL_FAILURE
+#define IP_PENDING                  (IP_STATUS_BASE + 255)
+
+//
+// Values used in the IP header Flags field.
+//
+#define IP_FLAG_DF      0x2         // Don't fragment this packet.
+
+//
+// Supported IP Option Types.
+//
+// These types define the options which may be used in the OptionsData field
+// of the ip_option_information structure.  See RFC 791 for a complete
+// description of each.
+//
+#define IP_OPT_EOL      0          // End of list option
+#define IP_OPT_NOP      1          // No operation
+#define IP_OPT_SECURITY 0x82       // Security option
+#define IP_OPT_LSRR     0x83       // Loose source route
+#define IP_OPT_SSRR     0x89       // Strict source route
+#define IP_OPT_RR       0x7        // Record route
+#define IP_OPT_TS       0x44       // Timestamp
+#define IP_OPT_SID      0x88       // Stream ID (obsolete)
+#define IP_OPT_ROUTER_ALERT 0x94  // Router Alert Option
+
+#define MAX_OPT_SIZE    40         // Maximum length of IP options in bytes
+
+//////////////////////////////////////////////////////////////////////
+// Global Constants and Types
+//////////////////////////////////////////////////////////////////////
+
+const char * const ICMP_DLL_NAME = "icmp.dll";
+const char * const ICMP_CREATE_FUNC = "IcmpCreateFile";
+const char * const ICMP_CLOSE_FUNC = "IcmpCloseHandle";
+const char * const ICMP_SEND_FUNC = "IcmpSendEcho";
+
+inline uint32 ReplySize(uint32 data_size) {
+  // A ping error message is 8 bytes long, so make sure we allow for at least
+  // 8 bytes of reply data.
+  return sizeof(ICMP_ECHO_REPLY) + talk_base::_max<uint32>(8, data_size);
+}
+
+//////////////////////////////////////////////////////////////////////
+// WinPing
+//////////////////////////////////////////////////////////////////////
+
+WinPing::WinPing()
+    : dll_(0), hping_(INVALID_HANDLE_VALUE), create_(0), close_(0), send_(0),
+      data_(0), dlen_(0), reply_(0), rlen_(0), valid_(false) {
+
+  dll_ = LoadLibraryA(ICMP_DLL_NAME);
+  if (!dll_) {
+    LOG(LERROR) << "LoadLibrary: " << GetLastError();
+    return;
+  }
+
+  create_ = (PIcmpCreateFile) GetProcAddress(dll_, ICMP_CREATE_FUNC);
+  close_ = (PIcmpCloseHandle) GetProcAddress(dll_, ICMP_CLOSE_FUNC);
+  send_ = (PIcmpSendEcho) GetProcAddress(dll_, ICMP_SEND_FUNC);
+  if (!create_ || !close_ || !send_) {
+    LOG(LERROR) << "GetProcAddress(ICMP_*): " << GetLastError();
+    return;
+  }
+
+  hping_ = create_();
+  if (hping_ == INVALID_HANDLE_VALUE) {
+    LOG(LERROR) << "IcmpCreateFile: " << GetLastError();
+    return;
+  }
+
+  dlen_ = 0;
+  rlen_ = ReplySize(dlen_);
+  data_ = new char[dlen_];
+  reply_ = new char[rlen_];
+
+  valid_ = true;
+}
+
+WinPing::~WinPing() {
+  if (dll_)
+    FreeLibrary(dll_);
+
+  if ((hping_ != INVALID_HANDLE_VALUE) && close_) {
+    if (!close_(hping_))
+      LOG(WARNING) << "IcmpCloseHandle: " << GetLastError();
+  }
+
+  delete[] data_;
+  delete reply_;
+}
+
+WinPing::PingResult WinPing::Ping(
+    uint32 ip, uint32 data_size, uint32 timeout, uint8 ttl,
+    bool allow_fragments) {
+
+  assert(IsValid());
+
+  IP_OPTION_INFORMATION ipopt;
+  memset(&ipopt, 0, sizeof(ipopt));
+  if (!allow_fragments)
+    ipopt.Flags |= IP_FLAG_DF;
+  ipopt.Ttl = ttl;
+
+  uint32 reply_size = ReplySize(data_size);
+
+  if (data_size > dlen_) {
+    delete [] data_;
+    dlen_ = data_size;
+    data_ = new char[dlen_];
+    memset(data_, 'z', dlen_);
+  }
+
+  if (reply_size > rlen_) {
+    delete [] reply_;
+    rlen_ = reply_size;
+    reply_ = new char[rlen_];
+  }
+
+  DWORD result = send_(hping_, talk_base::HostToNetwork32(ip),
+                       data_, uint16(data_size), &ipopt,
+                       reply_, reply_size, timeout);
+  if (result == 0) {
+    long error = GetLastError();
+    if (error == IP_PACKET_TOO_BIG)
+      return PING_TOO_LARGE;
+    if (error == IP_REQ_TIMED_OUT)
+      return PING_TIMEOUT;
+    LOG(LERROR) << "IcmpSendEcho(" << talk_base::SocketAddress::IPToString(ip)
+                << ", " << data_size << "): " << error;
+    return PING_FAIL;
+  }
+
+  return PING_SUCCESS;
+}
+
+//////////////////////////////////////////////////////////////////////
+// Microsoft Documenation
+//////////////////////////////////////////////////////////////////////
+//
+// Routine Name:
+//
+//     IcmpCreateFile
+//
+// Routine Description:
+//
+//     Opens a handle on which ICMP Echo Requests can be issued.
+//
+// Arguments:
+//
+//     None.
+//
+// Return Value:
+//
+//     An open file handle or INVALID_HANDLE_VALUE. Extended error information
+//     is available by calling GetLastError().
+//
+//////////////////////////////////////////////////////////////////////
+//
+// Routine Name:
+//
+//     IcmpCloseHandle
+//
+// Routine Description:
+//
+//     Closes a handle opened by ICMPOpenFile.
+//
+// Arguments:
+//
+//     IcmpHandle  - The handle to close.
+//
+// Return Value:
+//
+//     TRUE if the handle was closed successfully, otherwise FALSE. Extended
+//     error information is available by calling GetLastError().
+//
+//////////////////////////////////////////////////////////////////////
+//
+// Routine Name:
+//
+//     IcmpSendEcho
+//
+// Routine Description:
+//
+//     Sends an ICMP Echo request and returns any replies. The
+//     call returns when the timeout has expired or the reply buffer
+//     is filled.
+//
+// Arguments:
+//
+//     IcmpHandle           - An open handle returned by ICMPCreateFile.
+//
+//     DestinationAddress   - The destination of the echo request.
+//
+//     RequestData          - A buffer containing the data to send in the
+//                            request.
+//
+//     RequestSize          - The number of bytes in the request data buffer.
+//
+//     RequestOptions       - Pointer to the IP header options for the request.
+//                            May be NULL.
+//
+//     ReplyBuffer          - A buffer to hold any replies to the request.
+//                            On return, the buffer will contain an array of
+//                            ICMP_ECHO_REPLY structures followed by the
+//                            options and data for the replies. The buffer
+//                            should be large enough to hold at least one
+//                            ICMP_ECHO_REPLY structure plus
+//                            MAX(RequestSize, 8) bytes of data since an ICMP
+//                            error message contains 8 bytes of data.
+//
+//     ReplySize            - The size in bytes of the reply buffer.
+//
+//     Timeout              - The time in milliseconds to wait for replies.
+//
+// Return Value:
+//
+//     Returns the number of ICMP_ECHO_REPLY structures stored in ReplyBuffer.
+//     The status of each reply is contained in the structure. If the return
+//     value is zero, extended error information is available via
+//     GetLastError().
+//
+//////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
diff --git a/talk/base/winping.h b/talk/base/winping.h
new file mode 100644
index 0000000..35d36e3
--- /dev/null
+++ b/talk/base/winping.h
@@ -0,0 +1,97 @@
+/*
+ * 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_BASE_WINPING_H__
+#define TALK_BASE_WINPING_H__
+
+#ifdef WIN32
+
+#include "talk/base/win32.h"
+#include "talk/base/basictypes.h"
+
+namespace talk_base {
+
+// This class wraps a Win32 API for doing ICMP pinging.  This API, unlike the
+// the normal socket APIs (as implemented on Win9x), will return an error if
+// an ICMP packet with the dont-fragment bit set is too large.  This means this
+// class can be used to detect the MTU to a given address.
+
+typedef struct ip_option_information {
+    UCHAR   Ttl;                // Time To Live
+    UCHAR   Tos;                // Type Of Service
+    UCHAR   Flags;              // IP header flags
+    UCHAR   OptionsSize;        // Size in bytes of options data
+    PUCHAR  OptionsData;        // Pointer to options data
+} IP_OPTION_INFORMATION, * PIP_OPTION_INFORMATION;
+
+typedef HANDLE (WINAPI *PIcmpCreateFile)();
+
+typedef BOOL (WINAPI *PIcmpCloseHandle)(HANDLE icmp_handle);
+
+typedef DWORD (WINAPI *PIcmpSendEcho)(
+    HANDLE                   IcmpHandle,
+    ULONG                    DestinationAddress,
+    LPVOID                   RequestData,
+    WORD                     RequestSize,
+    PIP_OPTION_INFORMATION   RequestOptions,
+    LPVOID                   ReplyBuffer,
+    DWORD                    ReplySize,
+    DWORD                    Timeout);
+
+class WinPing {
+public:
+    WinPing();
+    ~WinPing();
+
+    // Determines whether the class was initialized correctly.
+    bool IsValid() { return valid_; }
+
+    // Attempts to send a ping with the given parameters.
+    enum PingResult { PING_FAIL, PING_TOO_LARGE, PING_TIMEOUT, PING_SUCCESS };
+    PingResult Ping(
+        uint32 ip, uint32 data_size, uint32 timeout_millis, uint8 ttl,
+        bool allow_fragments);
+
+private:
+    HMODULE dll_;
+    HANDLE hping_;
+    PIcmpCreateFile create_;
+    PIcmpCloseHandle close_;
+    PIcmpSendEcho send_;
+    char* data_;
+    uint32 dlen_;
+    char* reply_;
+    uint32 rlen_;
+    bool valid_;
+};
+
+} // namespace talk_base
+
+#endif // WIN32
+
+#endif // TALK_BASE_WINPING_H__
+
diff --git a/talk/examples/call/call_main.cc b/talk/examples/call/call_main.cc
new file mode 100644
index 0000000..4312399
--- /dev/null
+++ b/talk/examples/call/call_main.cc
@@ -0,0 +1,391 @@
+/*
+ * 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 <time.h>
+#include <iomanip>
+#include <cstdio>
+#include <cstring>
+#include <vector>
+#include "talk/base/logging.h"
+#include "talk/base/flags.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/stream.h"
+#include "talk/base/ssladapter.h"
+#include "talk/base/win32socketserver.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/xmpp/xmppclientsettings.h"
+#include "talk/examples/login/xmppthread.h"
+#include "talk/examples/login/xmppauth.h"
+#include "talk/examples/login/xmpppump.h"
+#include "talk/examples/call/callclient.h"
+#include "talk/examples/call/console.h"
+#include "talk/session/phone/filemediaengine.h"
+#include "talk/session/phone/mediasessionclient.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;
+
+
+cricket::MediaEngine* 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.
+  std::vector<cricket::AudioCodec> voice_codecs;
+  voice_codecs.push_back(
+      cricket::AudioCodec(9, "G722", 16000, 0, 1, 0));
+  file_media_engine->set_voice_codecs(voice_codecs);
+  std::vector<cricket::VideoCodec> video_codecs;
+  video_codecs.push_back(
+      cricket::VideoCodec(97, "H264", 320, 240, 30, 0));
+  file_media_engine->set_video_codecs(video_codecs);
+
+  return file_media_engine;
+}
+
+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_bool(a, false, "Turn on auto accept.");
+  DEFINE_bool(d, false, "Turn on debugging.");
+  DEFINE_string(
+      protocol, "hybrid",
+      "Initial signaling protocol to use: jingle, gingle, or hybrid.");
+  DEFINE_string(
+      secure, "disable",
+      "Disable or enable encryption: disable, enable, require.");
+  DEFINE_bool(testserver, false, "Use test server");
+  DEFINE_bool(plainserver, false, "Turn off tls and allow plain password.");
+  DEFINE_int(portallocator, 0, "Filter out unwanted connection types.");
+  DEFINE_string(filterhost, NULL, "Filter out the host from all candidates.");
+  DEFINE_string(pmuc, "groupchat.google.com", "The persistant muc domain.");
+  DEFINE_string(s, "talk.google.com", "The connection server to use.");
+  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(help, false, "Prints this message");
+
+  // parse options
+  FlagList::SetFlagsFromCommandLine(&argc, argv, true);
+  if (FLAG_help) {
+    FlagList::Print(NULL, false);
+    return 0;
+  }
+
+  bool auto_accept = FLAG_a;
+  bool debug = FLAG_d;
+  std::string protocol = FLAG_protocol;
+  bool test_server = FLAG_testserver;
+  bool plain_server = FLAG_plainserver;
+  int32 portallocator_flags = FLAG_portallocator;
+  std::string pmuc_domain = FLAG_pmuc;
+  std::string server = FLAG_s;
+  std::string secure = FLAG_secure;
+
+  cricket::SignalingProtocol initial_protocol = cricket::PROTOCOL_HYBRID;
+  if (protocol == "jingle") {
+    initial_protocol = cricket::PROTOCOL_JINGLE;
+  } else if (protocol == "gingle") {
+    initial_protocol = cricket::PROTOCOL_GINGLE;
+  } else if (protocol == "hybrid") {
+    initial_protocol = cricket::PROTOCOL_HYBRID;
+  } else {
+    printf("Invalid protocol.  Must be jingle, gingle, or hybrid.\n");
+    return 1;
+  }
+
+  cricket::SecureMediaPolicy secure_policy = cricket::SEC_DISABLED;
+  if (secure == "disable") {
+    secure_policy = cricket::SEC_DISABLED;
+  } else if (secure == "enable") {
+    secure_policy = cricket::SEC_ENABLED;
+  } else if (secure == "require") {
+    secure_policy = cricket::SEC_REQUIRED;
+  } else {
+    printf("Invalid encryption.  Must be enable, disable, or require.\n");
+    return 1;
+  }
+
+  // parse username and password, if present
+  buzz::Jid jid;
+  std::string username;
+  talk_base::InsecureCryptStringImpl pass;
+  if (argc > 1) {
+    username = argv[1];
+    if (argc > 2) {
+      pass.password() = argv[2];
+    }
+  }
+
+  if (debug)
+    talk_base::LogMessage::LogToDebug(talk_base::LS_VERBOSE);
+
+  if (username.empty()) {
+    std::cout << "JID: ";
+    std::cin >> username;
+  }
+  if (username.find('@') == std::string::npos) {
+    username.append("@localhost");
+  }
+  jid = buzz::Jid(username);
+  if (!jid.IsValid() || jid.node() == "") {
+    printf("Invalid JID. JIDs should be in the form user@domain\n");
+    return 1;
+  }
+  if (pass.password().empty() && !test_server) {
+    Console::SetEcho(false);
+    std::cout << "Password: ";
+    std::cin >> pass.password();
+    Console::SetEcho(true);
+    std::cout << std::endl;
+  }
+
+  buzz::XmppClientSettings xcs;
+  xcs.set_user(jid.node());
+  xcs.set_resource("call");
+  xcs.set_host(jid.domain());
+  xcs.set_use_tls(!test_server);
+
+  if (plain_server) {
+    xcs.set_use_tls(false);
+    xcs.set_allow_plain(true);
+  }
+  if (test_server) {
+    pass.password() = jid.node();
+    xcs.set_allow_plain(true);
+  }
+  xcs.set_pass(talk_base::CryptString(pass));
+
+  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(talk_base::SocketAddress(host, port));
+  printf("Logging in to %s as %s\n", server.c_str(), jid.Str().c_str());
+
+  talk_base::InitializeSSL();
+
+
+#if WIN32
+  // Need to pump messages on our main thread on Windows.
+  talk_base::Win32Thread w32_thread;
+  talk_base::ThreadManager::SetCurrent(&w32_thread);
+#endif
+  talk_base::Thread* main_thread = talk_base::Thread::Current();
+
+  XmppPump pump;
+  CallClient *client = new CallClient(pump.client());
+
+  if (FLAG_voiceinput || FLAG_voiceoutput ||
+      FLAG_videoinput || FLAG_videooutput) {
+    // If any dump file is specified, we use FileMediaEngine.
+    cricket::MediaEngine* engine = CreateFileMediaEngine(FLAG_voiceinput,
+                                                         FLAG_voiceoutput,
+                                                         FLAG_videoinput,
+                                                         FLAG_videooutput);
+    // The engine will be released by the client later.
+    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->SetInitialProtocol(initial_protocol);
+  client->SetSecurePolicy(secure_policy);
+  console->Start();
+
+  if (debug) {
+    pump.client()->SignalLogInput.connect(&debug_log_, &DebugLog::Input);
+    pump.client()->SignalLogOutput.connect(&debug_log_, &DebugLog::Output);
+  }
+
+  pump.DoLogin(xcs, new XmppSocket(true), NULL);
+  main_thread->Run();
+  pump.DoDisconnect();
+
+  console->Stop();
+  delete console;
+  delete client;
+
+  return 0;
+}
diff --git a/talk/examples/call/callclient.cc b/talk/examples/call/callclient.cc
new file mode 100644
index 0000000..94581e7
--- /dev/null
+++ b/talk/examples/call/callclient.cc
@@ -0,0 +1,922 @@
+/*
+ * 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/base/basicpacketsocketfactory.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/network.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/thread.h"
+#include "talk/examples/call/console.h"
+#include "talk/examples/call/presencepushtask.h"
+#include "talk/examples/call/presenceouttask.h"
+#include "talk/examples/call/mucinviterecvtask.h"
+#include "talk/examples/call/mucinvitesendtask.h"
+#include "talk/examples/call/friendinvitesendtask.h"
+#include "talk/examples/call/muc.h"
+#include "talk/examples/call/voicemailjidrequester.h"
+#include "talk/p2p/base/sessionmanager.h"
+#include "talk/p2p/client/basicportallocator.h"
+#include "talk/p2p/client/sessionmanagertask.h"
+#include "talk/session/phone/devicemanager.h"
+#include "talk/session/phone/mediaengine.h"
+#include "talk/session/phone/mediasessionclient.h"
+#include "talk/xmpp/constants.h"
+
+
+class NullRenderer : public cricket::VideoRenderer {
+ public:
+  explicit NullRenderer(const char* s) : s_(s) {}
+ private:
+  bool SetSize(int width, int height, int reserved) {
+    LOG(LS_INFO) << "Video size for " << s_ << ": " << width << "x" << height;
+    return true;
+  }
+  bool RenderFrame(const cricket::VideoFrame *frame) {
+    return true;
+  }
+  const char* s_;
+};
+
+namespace {
+
+const char* DescribeStatus(buzz::Status::Show show, const std::string& desc) {
+  switch (show) {
+  case buzz::Status::SHOW_XA:      return desc.c_str();
+  case buzz::Status::SHOW_ONLINE:  return "online";
+  case buzz::Status::SHOW_AWAY:    return "away";
+  case buzz::Status::SHOW_DND:     return "do not disturb";
+  case buzz::Status::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 && talk_base::FromString(words[index], &val)) {
+    return val;
+  } else {
+    return def;
+  }
+}
+
+
+}  // namespace
+
+const char* CALL_COMMANDS =
+"Available commands:\n"
+"\n"
+"  hangup  Ends the call.\n"
+"  mute    Stops sending voice.\n"
+"  unmute  Re-starts sending voice.\n"
+"  dtmf    Sends a DTMF tone.\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"
+"  voicemail [jid]     Leave a voicemail for the user with the given JID.\n"
+"  join [room]         Joins a multi-user-chat.\n"
+"  invite user [room]  Invites a friend to a multi-user-chat.\n"
+"  leave [room]        Leaves a multi-user-chat.\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);
+      Accept(options);
+    } else if (command == "reject") {
+      Reject();
+    } else {
+      console_->Print(RECEIVE_COMMANDS);
+    }
+  } else if (call_) {
+    if (command == "hangup") {
+      // TODO: do more shutdown here, move to Terminate()
+      call_->Terminate();
+      call_ = NULL;
+      session_ = NULL;
+      console_->SetPrompt(NULL);
+    } else if (command == "mute") {
+      call_->Mute(true);
+    } else if (command == "unmute") {
+      call_->Mute(false);
+    } else if ((command == "dtmf") && (words.size() == 2)) {
+      int ev = std::string("0123456789*#").find(words[1][0]);
+      call_->PressDTMF(ev);
+    } else {
+      console_->Print(CALL_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_->Printf(
+            "Invalid JID. JIDs should be in the form user@domain\n");
+      }
+    } else if ((words.size() == 2) && (command == "friend")) {
+      InviteFriend(words[1]);
+    } else if (command == "call") {
+      std::string to = GetWord(words, 1, "");
+      MakeCallTo(to, cricket::CallOptions());
+    } else if (command == "vcall") {
+      std::string to = GetWord(words, 1, "");
+      int bandwidth = GetInt(words, 2, cricket::kAutoBandwidth);
+      cricket::CallOptions options;
+      options.is_video = true;
+      options.video_bandwidth = bandwidth;
+      MakeCallTo(to, options);
+    } else if (command == "join") {
+      JoinMuc(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 == "getdevs") {
+      GetDevices();
+    } else if ((words.size() == 2) && (command == "setvol")) {
+      SetVolume(words[1]);
+    } else if (command == "voicemail") {
+      CallVoicemail((words.size() >= 2) ? words[1] : "");
+    } else {
+      console_->Print(CONSOLE_COMMANDS);
+    }
+  }
+}
+
+CallClient::CallClient(buzz::XmppClient* xmpp_client)
+    : xmpp_client_(xmpp_client),
+      media_engine_(NULL),
+      media_client_(NULL),
+      call_(NULL),
+      incoming_call_(false),
+      auto_accept_(false),
+      pmuc_domain_("groupchat.google.com"),
+      local_renderer_(NULL),
+      remote_renderer_(NULL),
+      roster_(new RosterMap),
+      portallocator_flags_(0),
+      allow_local_ips_(false),
+      initial_protocol_(cricket::PROTOCOL_HYBRID),
+      secure_policy_(cricket::SEC_DISABLED) {
+  xmpp_client_->SignalStateChange.connect(this, &CallClient::OnStateChange);
+}
+
+CallClient::~CallClient() {
+  delete media_client_;
+  delete roster_;
+}
+
+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) {
+  if (call == call_) {
+    if (remote_renderer_) {
+      delete remote_renderer_;
+      remote_renderer_ = NULL;
+    }
+    if (local_renderer_) {
+      delete local_renderer_;
+      local_renderer_ = NULL;
+    }
+    console_->SetPrompt(NULL);
+    console_->Print("call destroyed");
+    call_ = NULL;
+    session_ = NULL;
+  }
+}
+
+void CallClient::OnStateChange(buzz::XmppEngine::State state) {
+  switch (state) {
+  case buzz::XmppEngine::STATE_START:
+    console_->Print("connecting...");
+    break;
+
+  case buzz::XmppEngine::STATE_OPENING:
+    console_->Print("logging in...");
+    break;
+
+  case buzz::XmppEngine::STATE_OPEN:
+    console_->Print("logged in...");
+    InitPhone();
+    InitPresence();
+    break;
+
+  case buzz::XmppEngine::STATE_CLOSED:
+    buzz::XmppEngine::Error error = xmpp_client_->GetError(NULL);
+    console_->Print("logged out..." + strerror(error));
+    Quit();
+  }
+}
+
+void CallClient::InitPhone() {
+  std::string client_unique = xmpp_client_->jid().Str();
+  talk_base::InitRandom(client_unique.c_str(), client_unique.size());
+
+  worker_thread_ = new talk_base::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_| and |socket_factory_| are never
+  // deleted.
+
+  network_manager_ = new talk_base::NetworkManager();
+  socket_factory_ = new talk_base::BasicPacketSocketFactory(worker_thread_);
+
+  // TODO: Decide if the relay address should be specified here.
+  talk_base::SocketAddress stun_addr("stun.l.google.com", 19302);
+  port_allocator_ = new cricket::BasicPortAllocator(
+      network_manager_, socket_factory_, stun_addr,
+      talk_base::SocketAddress(), talk_base::SocketAddress(),
+      talk_base::SocketAddress());
+
+  if (portallocator_flags_ != 0) {
+    port_allocator_->set_flags(portallocator_flags_);
+  }
+  session_manager_ = new cricket::SessionManager(
+      port_allocator_, worker_thread_);
+  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::MediaEngine::Create();
+  }
+
+  media_client_ = new cricket::MediaSessionClient(
+      xmpp_client_->jid(),
+      session_manager_,
+      media_engine_,
+      new cricket::DeviceManager());
+  media_client_->SignalCallCreate.connect(this, &CallClient::OnCallCreate);
+  media_client_->SignalDevicesChange.connect(this,
+                                             &CallClient::OnDevicesChange);
+  media_client_->set_secure(secure_policy_);
+}
+
+void CallClient::OnRequestSignaling() {
+  session_manager_->OnSignalingReady();
+}
+
+void CallClient::OnSessionCreate(cricket::Session* session, bool initiate) {
+  session->set_allow_local_ips(allow_local_ips_);
+  session->set_current_protocol(initial_protocol_);
+}
+
+void CallClient::OnCallCreate(cricket::Call* call) {
+  call->SignalSessionState.connect(this, &CallClient::OnSessionState);
+  if (call->video()) {
+    local_renderer_ = new NullRenderer("local");
+    remote_renderer_ = new NullRenderer("remote");
+  }
+}
+
+void CallClient::OnSessionState(cricket::Call* call,
+                                cricket::BaseSession* session,
+                                cricket::BaseSession::State state) {
+  if (state == cricket::Session::STATE_RECEIVEDINITIATE) {
+    buzz::Jid jid(session->remote_name());
+    console_->Printf("Incoming call from '%s'", jid.Str().c_str());
+    call_ = call;
+    session_ = session;
+    incoming_call_ = true;
+    cricket::CallOptions options;
+    if (auto_accept_) {
+      Accept(options);
+    }
+  } else if (state == cricket::Session::STATE_SENTINITIATE) {
+    console_->Print("calling...");
+  } else if (state == cricket::Session::STATE_RECEIVEDACCEPT) {
+    console_->Print("call answered");
+  } else if (state == cricket::Session::STATE_RECEIVEDREJECT) {
+    console_->Print("call not answered");
+  } else if (state == cricket::Session::STATE_INPROGRESS) {
+    console_->Print("call in progress");
+  } else if (state == cricket::Session::STATE_RECEIVEDTERMINATE) {
+    console_->Print("other side hung up");
+  }
+}
+
+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_);
+  RefreshStatus();
+  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();
+}
+
+void CallClient::RefreshStatus() {
+  int media_caps = media_client_->GetCapabilities();
+  my_status_.set_jid(xmpp_client_->jid());
+  my_status_.set_available(true);
+  my_status_.set_show(buzz::Status::SHOW_ONLINE);
+  my_status_.set_priority(0);
+  my_status_.set_know_capabilities(true);
+  my_status_.set_pmuc_capability(true);
+  my_status_.set_phone_capability(
+      (media_caps & cricket::MediaEngine::AUDIO_RECV) != 0);
+  my_status_.set_video_capability(
+      (media_caps & cricket::MediaEngine::VIDEO_RECV) != 0);
+  my_status_.set_camera_capability(
+      (media_caps & cricket::MediaEngine::VIDEO_SEND) != 0);
+  my_status_.set_is_google_client(true);
+  my_status_.set_version("1.0.0.67");
+  presence_out_->Send(my_status_);
+}
+
+void CallClient::OnStatusUpdate(const buzz::Status& 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.phone_capability()) {
+     console_->Printf("Adding to roster: %s", key.c_str());
+    (*roster_)[key] = item;
+  } else {
+    console_->Printf("Removing from roster: %s", key.c_str());
+    RosterMap::iterator iter = roster_->find(key);
+    if (iter != roster_->end())
+      roster_->erase(iter);
+  }
+}
+
+void CallClient::PrintRoster() {
+  console_->SetPrompting(false);
+  console_->Printf("Roster contains %d callable", roster_->size());
+  RosterMap::iterator iter = roster_->begin();
+  while (iter != roster_->end()) {
+    console_->Printf("%s - %s",
+                     iter->second.jid.BareJid().Str().c_str(),
+                     DescribeStatus(iter->second.show, iter->second.status));
+    iter++;
+  }
+  console_->SetPrompting(true);
+}
+
+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, talk_base::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::InviteFriend(const std::string& name) {
+  buzz::Jid jid(name);
+  if (!jid.IsValid() || jid.node() == "") {
+    console_->Printf("Invalid JID. JIDs should be in the form user@domain\n");
+    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_->Printf("Requesting to befriend %s.\n", name.c_str());
+}
+
+void CallClient::MakeCallTo(const std::string& name,
+                            const cricket::CallOptions& given_options) {
+  // Copy so we can change .is_muc.
+  cricket::CallOptions options = given_options;
+
+  bool found = false;
+  options.is_muc = false;
+  buzz::Jid callto_jid(name);
+  buzz::Jid found_jid;
+  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 if (callto_jid.resource() == "voicemail") {
+    // if the resource is /voicemail, allow that
+    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_->Printf("Found %s '%s'", options.is_muc ? "room" : "online friend",
+        found_jid.Str().c_str());
+    PlaceCall(found_jid, options);
+  } else {
+    console_->Printf("Could not find online friend '%s'", name.c_str());
+  }
+}
+
+void CallClient::PlaceCall(const buzz::Jid& jid,
+                           const cricket::CallOptions& options) {
+  media_client_->SignalCallDestroy.connect(
+      this, &CallClient::OnCallDestroy);
+  if (!call_) {
+    call_ = media_client_->CreateCall();
+    console_->SetPrompt(jid.Str().c_str());
+    session_ = call_->InitiateSession(jid, options);
+    if (options.is_muc) {
+      // If people in this room are already in a call, must add all their
+      // streams.
+      buzz::Muc::MemberMap& members = mucs_[jid]->members();
+      for (buzz::Muc::MemberMap::iterator elem = members.begin();
+           elem != members.end();
+           ++elem) {
+        AddStream(elem->second.audio_src_id(), elem->second.video_src_id());
+      }
+    }
+  }
+  media_client_->SetFocus(call_);
+  if (call_->video()) {
+    call_->SetLocalRenderer(local_renderer_);
+    // TODO: Call this once for every different remote SSRC
+    // once we get to testing multiway video.
+    call_->SetVideoRenderer(session_, 0, remote_renderer_);
+  }
+}
+
+void CallClient::CallVoicemail(const std::string& name) {
+  buzz::Jid jid(name);
+  if (!jid.IsValid() || jid.node() == "") {
+    console_->Printf("Invalid JID. JIDs should be in the form user@domain\n");
+    return;
+  }
+  buzz::VoicemailJidRequester *request =
+    new buzz::VoicemailJidRequester(xmpp_client_, jid, my_status_.jid());
+  request->SignalGotVoicemailJid.connect(this,
+                                         &CallClient::OnFoundVoicemailJid);
+  request->SignalVoicemailJidError.connect(this,
+                                           &CallClient::OnVoicemailJidError);
+  request->Start();
+}
+
+void CallClient::OnFoundVoicemailJid(const buzz::Jid& to,
+                                     const buzz::Jid& voicemail) {
+  console_->Printf("Calling %s's voicemail.\n", to.Str().c_str());
+  PlaceCall(voicemail, cricket::CallOptions());
+}
+
+void CallClient::OnVoicemailJidError(const buzz::Jid& to) {
+  console_->Printf("Unable to voicemail %s.\n", to.Str().c_str());
+}
+
+void CallClient::AddStream(uint32 audio_src_id, uint32 video_src_id) {
+  if (audio_src_id || video_src_id) {
+    console_->Printf("Adding stream (%u, %u)\n", audio_src_id, video_src_id);
+    call_->AddStream(session_, audio_src_id, video_src_id);
+  }
+}
+
+void CallClient::RemoveStream(uint32 audio_src_id, uint32 video_src_id) {
+  if (audio_src_id || video_src_id) {
+    console_->Printf("Removing stream (%u, %u)\n", audio_src_id, video_src_id);
+    call_->RemoveStream(session_, audio_src_id, video_src_id);
+  }
+}
+
+void CallClient::Accept(const cricket::CallOptions& options) {
+  ASSERT(call_ && incoming_call_);
+  ASSERT(call_->sessions().size() == 1);
+  call_->AcceptSession(call_->sessions()[0], options);
+  media_client_->SetFocus(call_);
+  if (call_->video()) {
+    call_->SetLocalRenderer(local_renderer_);
+    // The client never does an accept for multiway, so this must be 1:1,
+    // so there's no SSRC.
+    call_->SetVideoRenderer(session_, 0, remote_renderer_);
+  }
+  incoming_call_ = false;
+}
+
+void CallClient::Reject() {
+  ASSERT(call_ && incoming_call_);
+  call_->RejectSession(call_->sessions()[0]);
+  incoming_call_ = false;
+}
+
+void CallClient::Quit() {
+  talk_base::Thread::Current()->Quit();
+}
+
+void CallClient::JoinMuc(const std::string& room) {
+  buzz::Jid room_jid;
+  if (room.length() > 0) {
+    room_jid = buzz::Jid(room);
+  } else {
+    // 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;
+      }
+    }
+
+    talk_base::sprintfn(guid_room, ARRAY_SIZE(guid_room),
+                        "private-chat-%s@%s", guid, pmuc_domain_.c_str());
+    room_jid = buzz::Jid(guid_room);
+  }
+
+  if (!room_jid.IsValid()) {
+    console_->Printf("Unable to make valid muc endpoint for %s", room.c_str());
+    return;
+  }
+
+  MucMap::iterator elem = mucs_.find(room_jid);
+  if (elem != mucs_.end()) {
+    console_->Printf("This MUC already exists.");
+    return;
+  }
+
+  buzz::Muc* muc = new buzz::Muc(room_jid, xmpp_client_->jid().node());
+  mucs_[room_jid] = muc;
+  presence_out_->SendDirected(muc->local_jid(), my_status_);
+}
+
+void CallClient::OnMucInviteReceived(const buzz::Jid& inviter,
+    const buzz::Jid& room,
+    const std::vector<buzz::AvailableMediaEntry>& avail) {
+
+  console_->Printf("Invited to join %s by %s.\n", room.Str().c_str(),
+      inviter.Str().c_str());
+  console_->Printf("Available media:\n");
+  if (avail.size() > 0) {
+    for (std::vector<buzz::AvailableMediaEntry>::const_iterator i =
+            avail.begin();
+        i != avail.end();
+        ++i) {
+      console_->Printf("  %s, %s\n",
+          buzz::AvailableMediaEntry::TypeAsString(i->type),
+          buzz::AvailableMediaEntry::StatusAsString(i->status));
+    }
+  } else {
+    console_->Printf("  None\n");
+  }
+  // We automatically join the room.
+  JoinMuc(room.Str());
+}
+
+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_->Printf("Joined \"%s\"", muc->jid().Str().c_str());
+}
+
+void CallClient::OnMucStatusUpdate(const buzz::Jid& jid,
+    const buzz::MucStatus& status) {
+
+  // Look up this muc.
+  MucMap::iterator elem = mucs_.find(jid);
+  ASSERT(elem != mucs_.end() &&
+         elem->second->state() == buzz::Muc::MUC_JOINED);
+
+  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()) {
+    // User is leaving the room.
+    buzz::Muc::MemberMap::iterator elem =
+      muc->members().find(status.jid().resource());
+
+    ASSERT(elem != muc->members().end());
+
+    // If user had src-ids, they have the left the room without explicitly
+    // hanging-up; must tear down the stream if in a call to this room.
+    if (call_ && session_->remote_name() == muc->jid().Str()) {
+      RemoveStream(elem->second.audio_src_id(), elem->second.video_src_id());
+    }
+
+    // Remove them from the room.
+    muc->members().erase(elem);
+  } else {
+    // Either user has joined or something changed about them.
+    // Note: The [] operator here will create a new entry if it does not
+    // exist, which is what we want.
+    buzz::MucStatus& member_status(
+        muc->members()[status.jid().resource()]);
+    if (call_ && session_->remote_name() == muc->jid().Str()) {
+      // We are in a call to this muc. Must potentially update our streams.
+      // The following code will correctly update our streams regardless of
+      // whether the SSRCs have been removed, added, or changed and regardless
+      // of whether that has been done to both or just one. This relies on the
+      // fact that AddStream/RemoveStream do nothing for SSRC arguments that are
+      // zero.
+      uint32 remove_audio_src_id = 0;
+      uint32 remove_video_src_id = 0;
+      uint32 add_audio_src_id = 0;
+      uint32 add_video_src_id = 0;
+      if (member_status.audio_src_id() != status.audio_src_id()) {
+        remove_audio_src_id = member_status.audio_src_id();
+        add_audio_src_id = status.audio_src_id();
+      }
+      if (member_status.video_src_id() != status.video_src_id()) {
+        remove_video_src_id = member_status.video_src_id();
+        add_video_src_id = status.video_src_id();
+      }
+      // Remove the old SSRCs, if any.
+      RemoveStream(remove_audio_src_id, remove_video_src_id);
+      // Add the new SSRCs, if any.
+      AddStream(add_audio_src_id, add_video_src_id);
+    }
+    // Update the status. This will use the compiler-generated copy
+    // constructor, which is perfectly adequate for this class.
+    member_status = status;
+  }
+}
+
+void CallClient::LeaveMuc(const std::string& room) {
+  buzz::Jid room_jid;
+  if (room.length() > 0) {
+    room_jid = buzz::Jid(room);
+  } else if (mucs_.size() > 0) {
+    // leave the first MUC if no JID specified
+    room_jid = mucs_.begin()->first;
+  }
+
+  if (!room_jid.IsValid()) {
+    console_->Printf("Invalid MUC JID.");
+    return;
+  }
+
+  MucMap::iterator elem = mucs_.find(room_jid);
+  if (elem == mucs_.end()) {
+    console_->Printf("No such MUC.");
+    return;
+  }
+
+  buzz::Muc* muc = elem->second;
+  muc->set_state(buzz::Muc::MUC_LEAVING);
+
+  buzz::Status 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_->Printf("Failed to join \"%s\", code=%d",
+                     muc->jid().Str().c_str(), error);
+  } else if (muc->state() == buzz::Muc::MUC_JOINED) {
+    console_->Printf("Kicked from \"%s\"",
+                     muc->jid().Str().c_str());
+  }
+
+  delete muc;
+  mucs_.erase(elem);
+}
+
+void CallClient::InviteToMuc(const std::string& user, const std::string& room) {
+  // First find the room.
+  const buzz::Muc* found_muc;
+  if (room.length() == 0) {
+    if (mucs_.size() == 0) {
+      console_->Printf("Not in a room yet; can't invite.\n");
+      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_->Printf("Not in room %s.\n", room.c_str());
+      return;
+    }
+    found_muc = elem->second;
+  }
+  // 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)) {
+      muc_invite_send_->Send(iter->second.jid, *found_muc);
+      found_user = true;
+    }
+  }
+  if (!found_user) {
+    console_->Printf("No such friend as %s.\n", user.c_str());
+    return;
+  }
+}
+
+void CallClient::GetDevices() {
+  std::vector<std::string> names;
+  media_client_->GetAudioInputDevices(&names);
+  printf("Audio input devices:\n");
+  PrintDevices(names);
+  media_client_->GetAudioOutputDevices(&names);
+  printf("Audio output devices:\n");
+  PrintDevices(names);
+  media_client_->GetVideoCaptureDevices(&names);
+  printf("Video capture devices:\n");
+  PrintDevices(names);
+}
+
+void CallClient::PrintDevices(const std::vector<std::string>& names) {
+  for (size_t i = 0; i < names.size(); ++i) {
+    printf("%d: %s\n", static_cast<int>(i), names[i].c_str());
+  }
+}
+
+void CallClient::OnDevicesChange() {
+  printf("Devices changed.\n");
+  RefreshStatus();
+}
+
+void CallClient::SetVolume(const std::string& level) {
+  media_client_->SetOutputVolume(strtol(level.c_str(), NULL, 10));
+}
diff --git a/talk/examples/call/callclient.h b/talk/examples/call/callclient.h
new file mode 100644
index 0000000..d2b806e
--- /dev/null
+++ b/talk/examples/call/callclient.h
@@ -0,0 +1,202 @@
+/*
+ * 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/p2p/base/session.h"
+#include "talk/session/phone/mediachannel.h"
+#include "talk/session/phone/mediasessionclient.h"
+#include "talk/xmpp/xmppclient.h"
+#include "talk/examples/call/status.h"
+#include "talk/examples/call/console.h"
+
+namespace buzz {
+class PresencePushTask;
+class PresenceOutTask;
+class MucInviteRecvTask;
+class MucInviteSendTask;
+class FriendInviteSendTask;
+class VoicemailJidRequester;
+class DiscoInfoQueryTask;
+class Muc;
+class Status;
+class MucStatus;
+struct AvailableMediaEntry;
+}
+
+namespace talk_base {
+class Thread;
+class NetworkManager;
+}
+
+namespace cricket {
+class PortAllocator;
+class MediaEngine;
+class MediaSessionClient;
+class Receiver;
+class Call;
+struct CallOptions;
+class SessionManagerTask;
+}
+
+struct RosterItem {
+  buzz::Jid jid;
+  buzz::Status::Show show;
+  std::string status;
+};
+
+class NullRenderer;
+
+class CallClient: public sigslot::has_slots<> {
+ public:
+  explicit CallClient(buzz::XmppClient* xmpp_client);
+  ~CallClient();
+
+  cricket::MediaSessionClient* media_client() const { return media_client_; }
+  void SetMediaEngine(cricket::MediaEngine* 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 SetConsole(Console *console) {
+    console_ = console;
+  }
+
+  void ParseLine(const std::string &str);
+
+  void SendChat(const std::string& to, const std::string msg);
+  void InviteFriend(const std::string& user);
+  void JoinMuc(const std::string& room);
+  void InviteToMuc(const std::string& user, const std::string& room);
+  void LeaveMuc(const std::string& room);
+  void SetPortAllocatorFlags(uint32 flags) { portallocator_flags_ = flags; }
+  void SetAllowLocalIps(bool allow_local_ips) {
+    allow_local_ips_ = allow_local_ips;
+  }
+
+  void SetInitialProtocol(cricket::SignalingProtocol initial_protocol) {
+    initial_protocol_ = initial_protocol;
+  }
+
+  void SetSecurePolicy(cricket::SecureMediaPolicy secure_policy) {
+    secure_policy_ = secure_policy;
+  }
+
+
+  typedef std::map<buzz::Jid, buzz::Muc*> MucMap;
+
+  const MucMap& mucs() const {
+    return mucs_;
+  }
+
+ 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 InitPhone();
+  void InitPresence();
+  void RefreshStatus();
+  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::BaseSession* session,
+                      cricket::BaseSession::State state);
+  void OnStatusUpdate(const buzz::Status& 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::MucStatus& status);
+  void OnMucLeft(const buzz::Jid& endpoint, int error);
+  void OnDevicesChange();
+  void OnFoundVoicemailJid(const buzz::Jid& to, const buzz::Jid& voicemail);
+  void OnVoicemailJidError(const buzz::Jid& to);
+
+  static const std::string strerror(buzz::XmppEngine::Error err);
+
+  void PrintRoster();
+  void MakeCallTo(const std::string& name, const cricket::CallOptions& options);
+  void PlaceCall(const buzz::Jid& jid, const cricket::CallOptions& options);
+  void CallVoicemail(const std::string& name);
+  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);
+
+  typedef std::map<std::string, RosterItem> RosterMap;
+
+  Console *console_;
+  buzz::XmppClient* xmpp_client_;
+  talk_base::Thread* worker_thread_;
+  talk_base::NetworkManager* network_manager_;
+  talk_base::PacketSocketFactory* socket_factory_;
+  cricket::PortAllocator* port_allocator_;
+  cricket::SessionManager* session_manager_;
+  cricket::SessionManagerTask* session_manager_task_;
+  cricket::MediaEngine* media_engine_;
+  cricket::MediaSessionClient* media_client_;
+  MucMap mucs_;
+
+  cricket::Call* call_;
+  cricket::BaseSession *session_;
+  bool incoming_call_;
+  bool auto_accept_;
+  std::string pmuc_domain_;
+  NullRenderer* local_renderer_;
+  NullRenderer* remote_renderer_;
+
+  buzz::Status 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 initial_protocol_;
+  cricket::SecureMediaPolicy secure_policy_;
+  std::string last_sent_to_;
+};
+
+#endif  // TALK_EXAMPLES_CALL_CALLCLIENT_H_
diff --git a/talk/examples/call/console.cc b/talk/examples/call/console.cc
new file mode 100644
index 0000000..0aa7a4f
--- /dev/null
+++ b/talk/examples/call/console.cc
@@ -0,0 +1,167 @@
+/*
+ * 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
+
+#ifdef POSIX
+#include <unistd.h>
+#endif  // POSIX
+#include <cassert>
+#include "talk/base/logging.h"
+#include "talk/base/messagequeue.h"
+#include "talk/base/stringutils.h"
+#include "talk/examples/call/console.h"
+#include "talk/examples/call/callclient.h"
+
+#ifdef POSIX
+#include <signal.h>
+
+static void DoNothing(int unused) {}
+#endif
+
+Console::Console(talk_base::Thread *thread, CallClient *client) :
+  client_(client), client_thread_(thread),
+  console_thread_(new talk_base::Thread()), prompt_(std::string("call")),
+  prompting_(true) {
+}
+
+Console::~Console() {
+  Stop();
+}
+
+void Console::Start() {
+  if (!console_thread_.get()) {
+    // stdin was closed in Stop(), so we can't restart.
+    LOG(LS_ERROR) << "Cannot re-start";
+    return;
+  }
+  if (console_thread_->started()) {
+    LOG(LS_WARNING) << "Already started";
+    return;
+  }
+  console_thread_->Start();
+  console_thread_->Post(this, MSG_START);
+}
+
+void Console::Stop() {
+  if (console_thread_.get() && console_thread_->started()) {
+#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();
+  }
+}
+
+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
+  int re;
+  if (on)
+    re = system("stty echo");
+  else
+    re = system("stty -echo");
+  if (-1 == re)
+    return;
+#endif
+}
+
+void Console::Print(const char* str) {
+  printf("\n%s", str);
+  if (prompting_)
+    printf("\n(%s) ", prompt_.c_str());
+}
+
+void Console::Print(const std::string& str) {
+  Print(str.c_str());
+}
+
+void Console::Printf(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';
+  Print(buf);
+
+  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 talk_base::TypedMessageData<std::string>(input_buffer));
+  }
+}
+
+void Console::OnMessage(talk_base::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:
+      talk_base::TypedMessageData<std::string> *data =
+          static_cast<talk_base::TypedMessageData<std::string>*>(msg->pdata);
+      client_->ParseLine(data->data());
+      break;
+  }
+}
diff --git a/talk/examples/call/console.h b/talk/examples/call/console.h
new file mode 100644
index 0000000..e2ba4f7
--- /dev/null
+++ b/talk/examples/call/console.h
@@ -0,0 +1,85 @@
+/*
+ * 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 <cstdio>
+
+#include "talk/base/thread.h"
+#include "talk/base/messagequeue.h"
+#include "talk/base/scoped_ptr.h"
+
+class CallClient;
+
+class Console : public talk_base::MessageHandler {
+ public:
+  Console(talk_base::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(talk_base::Message *msg);
+
+  void SetPrompt(const char *prompt) {
+    prompt_ = prompt ? std::string(prompt) : std::string("call");
+  }
+
+  void SetPrompting(bool prompting) {
+    prompting_ = prompting;
+    if (prompting)
+      printf("\n(%s) ", prompt_.c_str());
+  }
+
+  bool prompting() { return prompting_; }
+
+  void Print(const char* str);
+  void Print(const std::string& str);
+  void Printf(const char* format, ...);
+
+  static void SetEcho(bool on);
+
+ private:
+  enum {
+    MSG_START,
+    MSG_INPUT,
+  };
+
+  void RunConsole();
+  void ParseLine(std::string &str);
+
+  CallClient *client_;
+  talk_base::Thread *client_thread_;
+  talk_base::scoped_ptr<talk_base::Thread> console_thread_;
+  std::string prompt_;
+  bool prompting_;
+};
+
+#endif // TALK_EXAMPLES_CALL_CONSOLE_H_
diff --git a/talk/examples/call/discoitemsquerytask.cc b/talk/examples/call/discoitemsquerytask.cc
new file mode 100644
index 0000000..df6d96d
--- /dev/null
+++ b/talk/examples/call/discoitemsquerytask.cc
@@ -0,0 +1,97 @@
+/*
+ * 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/discoitemsquerytask.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/xmpp/constants.h"
+
+
+namespace buzz {
+
+namespace {
+const int kDiscoItemsTimeout = 60;
+} // namespace
+
+DiscoItemsQueryTask::DiscoItemsQueryTask(Task* parent,
+                                         const std::string node,
+                                         const Jid& to)
+    : XmppTask(parent, XmppEngine::HL_SINGLE), node_(node) {
+  set_timeout_seconds(kDiscoItemsTimeout);
+  to_ = to;
+}
+
+int DiscoItemsQueryTask::ProcessStart() {
+  talk_base::scoped_ptr<XmlElement> get(MakeIq(STR_GET, to_, task_id()));
+
+  XmlElement* element = new XmlElement(QN_DISCO_ITEMS_QUERY, true);
+  element->AddAttr(QN_NODE, node_);
+
+  get->AddElement(element);
+
+  if (SendStanza(get.get()) != XMPP_RETURN_OK) {
+    SignalDiscoItemsError(to_, NULL);
+    return STATE_ERROR;
+  }
+
+  return STATE_RESPONSE;
+}
+
+int DiscoItemsQueryTask::ProcessResponse() {
+  const XmlElement* stanza = NextStanza();
+  if (stanza == NULL)
+    return STATE_BLOCKED;
+
+  bool success = false;
+  if (stanza->Attr(QN_TYPE) != STR_ERROR) {
+    const XmlElement* query = stanza->FirstNamed(QN_DISCO_ITEMS_QUERY);
+    if (query) {
+      SignalGotDiscoItems(to_, query);
+      success = true;
+    }
+  }
+
+  if (!success) {
+    SignalDiscoItemsError(to_, stanza->FirstNamed(QN_ERROR));
+  }
+
+  return STATE_DONE;
+}
+
+int DiscoItemsQueryTask::OnTimeout() {
+  SignalDiscoItemsError(to_, NULL);
+  return XmppTask::OnTimeout();
+}
+
+bool DiscoItemsQueryTask::HandleStanza(const XmlElement* stanza) {
+  if (!MatchResponseIq(stanza, to_, task_id()))
+    return false;
+  QueueStanza(stanza);
+  return true;
+
+}
+
+}
diff --git a/talk/examples/call/discoitemsquerytask.h b/talk/examples/call/discoitemsquerytask.h
new file mode 100644
index 0000000..6be8c35
--- /dev/null
+++ b/talk/examples/call/discoitemsquerytask.h
@@ -0,0 +1,88 @@
+/*
+ * 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.
+ */
+
+// Fires a disco items query, such as the following example:
+// 
+//      <iq type='get'
+//          from='foo@gmail.com/asdf'
+//          to='bar@google.com'
+//          id='1234'>
+//          <query xmlns=' http://jabber.org/protocol/disco#items'
+//                 node='blah '/>
+//      </iq>
+//
+// Sample response:
+//
+//      <iq type='result'
+//          from=' hendriks@google.com'
+//          to='rsturgell@google.com/asdf'
+//          id='1234'>
+//          <query xmlns=' http://jabber.org/protocol/disco#items '
+//                 node='blah'>
+//                 <item something='somethingelse'/>
+//          </query>
+//      </iq>
+
+
+#ifndef _DISCOITEMSQUERYTASK_H_
+#define _DISCOITEMSQUERYTASK_H_
+
+#include "talk/xmpp/xmpptask.h"
+
+namespace buzz {
+
+class DiscoItemsQueryTask : public XmppTask {
+ public:
+  // TODO: Currently, this only supports one query stanza - we may eventually
+  // need it to support multiple
+  DiscoItemsQueryTask(Task* parent, const std::string node, const Jid& to);
+
+  virtual int ProcessStart();
+  virtual int ProcessResponse();
+
+  // On success, fires a signal with the jid we sent the query to and the inner
+  // XmlElement
+  sigslot::signal2<Jid, const XmlElement*> SignalGotDiscoItems;
+
+  // The XmlElement here is the error element under the error response.  If the
+  // request just timed out then this will be NULL
+  sigslot::signal2<Jid, const XmlElement*> SignalDiscoItemsError;
+
+ protected:
+  virtual bool HandleStanza(const XmlElement* stanza);
+  virtual int OnTimeout();
+  
+ private:
+  // The jid we're querying
+  Jid to_;
+  // The name of the node
+  const std::string node_;
+};
+
+}
+
+#endif
diff --git a/talk/examples/call/friendinvitesendtask.cc b/talk/examples/call/friendinvitesendtask.cc
new file mode 100644
index 0000000..cdb0b2c
--- /dev/null
+++ b/talk/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/xmpp/constants.h"
+#include "talk/examples/call/friendinvitesendtask.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/talk/examples/call/friendinvitesendtask.h b/talk/examples/call/friendinvitesendtask.h
new file mode 100644
index 0000000..3f776bb
--- /dev/null
+++ b/talk/examples/call/friendinvitesendtask.h
@@ -0,0 +1,48 @@
+/*
+ * 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 "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/xmpptask.h"
+
+namespace buzz {
+
+class FriendInviteSendTask : public XmppTask {
+public:
+  FriendInviteSendTask(Task* parent) : XmppTask(parent) {}
+  virtual ~FriendInviteSendTask() {}
+
+  XmppReturnStatus Send(const Jid& user);
+
+  virtual int ProcessStart();
+};
+
+}
+
+#endif
diff --git a/talk/examples/call/muc.h b/talk/examples/call/muc.h
new file mode 100644
index 0000000..2f6df2e
--- /dev/null
+++ b/talk/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 "talk/xmpp/jid.h"
+#include "talk/examples/call/status.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, MucStatus> 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/talk/examples/call/mucinviterecvtask.cc b/talk/examples/call/mucinviterecvtask.cc
new file mode 100644
index 0000000..061db74
--- /dev/null
+++ b/talk/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/xmpp/constants.h"
+#include "talk/examples/call/mucinviterecvtask.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/talk/examples/call/mucinviterecvtask.h b/talk/examples/call/mucinviterecvtask.h
new file mode 100644
index 0000000..892b484
--- /dev/null
+++ b/talk/examples/call/mucinviterecvtask.h
@@ -0,0 +1,81 @@
+/*
+ * 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 "talk/base/sigslot.h"
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/xmpptask.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:
+  MucInviteRecvTask(Task* 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/talk/examples/call/mucinvitesendtask.cc b/talk/examples/call/mucinvitesendtask.cc
new file mode 100644
index 0000000..efd9a81
--- /dev/null
+++ b/talk/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 "talk/xmpp/constants.h"
+#include "talk/xmpp/xmppclient.h"
+
+namespace buzz {
+
+XmppReturnStatus
+MucInviteSendTask::Send(const Jid& user, const Muc& muc) {
+  if (GetState() != STATE_INIT && GetState() != STATE_START)
+    return XMPP_RETURN_BADSTATE;
+
+  XmlElement* message = new XmlElement(QN_MESSAGE);
+  message->AddAttr(QN_TO, muc.jid().Str());
+  XmlElement* xstanza = new XmlElement(QN_MUC_USER_X);
+  XmlElement* invite = new XmlElement(QN_MUC_USER_INVITE);
+  invite->AddAttr(QN_TO, user.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/talk/examples/call/mucinvitesendtask.h b/talk/examples/call/mucinvitesendtask.h
new file mode 100644
index 0000000..18e0f99
--- /dev/null
+++ b/talk/examples/call/mucinvitesendtask.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 _MUCINVITESENDTASK_H_
+#define _MUCINVITESENDTASK_H_
+
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/xmpptask.h"
+#include "talk/examples/call/muc.h"
+
+namespace buzz {
+
+class MucInviteSendTask : public XmppTask {
+public:
+  MucInviteSendTask(Task* parent) : XmppTask(parent) {}
+  virtual ~MucInviteSendTask() {}
+
+  XmppReturnStatus Send(const Jid& user, const Muc& muc);
+
+  virtual int ProcessStart();
+};
+
+}
+
+#endif
diff --git a/talk/examples/call/presenceouttask.cc b/talk/examples/call/presenceouttask.cc
new file mode 100644
index 0000000..ff3d91b
--- /dev/null
+++ b/talk/examples/call/presenceouttask.cc
@@ -0,0 +1,147 @@
+/*
+ * 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 <time.h>
+#include <sstream>
+#include "talk/base/stringencode.h"
+#include "talk/examples/call/presenceouttask.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/xmppclient.h"
+
+namespace buzz {
+
+XmppReturnStatus
+PresenceOutTask::Send(const Status & s) {
+  if (GetState() != STATE_INIT && GetState() != STATE_START)
+    return XMPP_RETURN_BADSTATE;
+
+  QueueStanza(TranslateStatus(s));
+  return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus
+PresenceOutTask::SendDirected(const Jid & j, const Status & s) {
+  if (GetState() != STATE_INIT && GetState() != STATE_START)
+    return XMPP_RETURN_BADSTATE;
+
+  XmlElement * presence = TranslateStatus(s);
+  presence->AddAttr(QN_TO, j.Str());
+  QueueStanza(presence);
+  return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus PresenceOutTask::SendProbe(const Jid & jid) {
+  if (GetState() != STATE_INIT && GetState() != STATE_START)
+    return XMPP_RETURN_BADSTATE;
+
+  XmlElement * presence = new XmlElement(QN_PRESENCE);
+  presence->AddAttr(QN_TO, jid.Str());
+  presence->AddAttr(QN_TYPE, "probe");
+
+  QueueStanza(presence);
+  return XMPP_RETURN_OK;
+}
+
+int
+PresenceOutTask::ProcessStart() {
+  const XmlElement * stanza = NextStanza();
+  if (stanza == NULL)
+    return STATE_BLOCKED;
+
+  if (SendStanza(stanza) != XMPP_RETURN_OK)
+    return STATE_ERROR;
+
+  return STATE_START;
+}
+
+XmlElement *
+PresenceOutTask::TranslateStatus(const Status & s) {
+  XmlElement * result = new XmlElement(QN_PRESENCE);
+  if (!s.available()) {
+    result->AddAttr(QN_TYPE, STR_UNAVAILABLE);
+  }
+  else {
+    if (s.show() != Status::SHOW_ONLINE && s.show() != Status::SHOW_OFFLINE) {
+      result->AddElement(new XmlElement(QN_SHOW));
+      switch (s.show()) {
+        default:
+          result->AddText(STR_SHOW_AWAY, 1);
+          break;
+        case Status::SHOW_XA:
+          result->AddText(STR_SHOW_XA, 1);
+          break;
+        case Status::SHOW_DND:
+          result->AddText(STR_SHOW_DND, 1);
+          break;
+        case Status::SHOW_CHAT:
+          result->AddText(STR_SHOW_CHAT, 1);
+          break;
+      }
+    }
+
+    result->AddElement(new XmlElement(QN_STATUS));
+    result->AddText(s.status(), 1);
+
+    std::string pri;
+    talk_base::ToString(s.priority(), &pri);
+
+    result->AddElement(new XmlElement(QN_PRIORITY));
+    result->AddText(pri, 1);
+
+    if (s.know_capabilities() && s.is_google_client()) {
+      result->AddElement(new XmlElement(QN_CAPS_C, true));
+      result->AddAttr(QN_NODE, GOOGLE_CLIENT_NODE, 1);
+      result->AddAttr(QN_VER, s.version(), 1);
+
+      std::string caps;
+      caps.append(s.phone_capability() ? "voice-v1" : "");
+      caps.append(s.pmuc_capability() ? " pmuc-v1" : "");
+      caps.append(s.video_capability() ? " video-v1" : "");
+      caps.append(s.camera_capability() ? " camera-v1" : "");
+
+      result->AddAttr(QN_EXT, caps, 1);
+    }
+
+    // Put the delay mark on the presence according to JEP-0091
+    {
+      result->AddElement(new XmlElement(kQnDelayX, true));
+
+      // This here is why we *love* the C runtime
+      time_t current_time_seconds;
+      time(&current_time_seconds);
+      struct tm* current_time = gmtime(&current_time_seconds);
+      char output[256];
+      strftime(output, ARRAY_SIZE(output), "%Y%m%dT%H:%M:%S", current_time);
+      result->AddAttr(kQnStamp, output, 1);
+    }
+  }
+
+  return result;
+}
+
+
+}
diff --git a/talk/examples/call/presenceouttask.h b/talk/examples/call/presenceouttask.h
new file mode 100644
index 0000000..36e7f15
--- /dev/null
+++ b/talk/examples/call/presenceouttask.h
@@ -0,0 +1,53 @@
+/*
+ * 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 _PRESENCEOUTTASK_H_
+#define _PRESENCEOUTTASK_H_
+
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/xmpptask.h"
+#include "talk/examples/call/status.h"
+
+namespace buzz {
+
+class PresenceOutTask : public XmppTask {
+public:
+  PresenceOutTask(Task * parent) : XmppTask(parent) {}
+  virtual ~PresenceOutTask() {}
+
+  XmppReturnStatus Send(const Status & s);
+  XmppReturnStatus SendDirected(const Jid & j, const Status & s);
+  XmppReturnStatus SendProbe(const Jid& jid);
+
+  virtual int ProcessStart();
+private:
+  XmlElement * TranslateStatus(const Status & s);
+};
+
+}
+
+#endif
diff --git a/talk/examples/call/presencepushtask.cc b/talk/examples/call/presencepushtask.cc
new file mode 100644
index 0000000..f820030
--- /dev/null
+++ b/talk/examples/call/presencepushtask.cc
@@ -0,0 +1,254 @@
+/*
+ * 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/base/stringencode.h"
+#include "talk/examples/call/muc.h"
+#include "talk/xmpp/constants.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;
+
+  Status 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);
+      if (elem) {
+        elem = elem->FirstNamed(QN_MUC_USER_STATUS);
+      }
+      if (elem && (elem->Attr(QN_CODE) == "110" ||
+          elem->Attr(QN_CODE) == "100")) {
+        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 {
+    MucStatus s;
+    FillMucStatus(from, stanza, &s);
+    SignalMucStatusUpdate(muc->jid(), s);
+  }
+}
+
+void PresencePushTask::FillStatus(const Jid& from, const XmlElement* stanza,
+                                  Status* 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 (talk_base::FromString(priority->BodyText(), &pri)) {
+        s->set_priority(pri);
+      }
+    }
+
+    const XmlElement * show = stanza->FirstNamed(QN_SHOW);
+    if (show == NULL || show->FirstChild() == NULL) {
+      s->set_show(Status::SHOW_ONLINE);
+    }
+    else {
+      if (show->BodyText() == "away") {
+        s->set_show(Status::SHOW_AWAY);
+      }
+      else if (show->BodyText() == "xa") {
+        s->set_show(Status::SHOW_XA);
+      }
+      else if (show->BodyText() == "dnd") {
+        s->set_show(Status::SHOW_DND);
+      }
+      else if (show->BodyText() == "chat") {
+        s->set_show(Status::SHOW_CHAT);
+      }
+      else {
+        s->set_show(Status::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);
+
+      if (node == GOOGLE_CLIENT_NODE) {
+        s->set_is_google_client(true);
+        s->set_version(ver);
+      }
+
+      if (ListContainsToken(exts, "voice-v1")) {
+        s->set_phone_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);
+    }
+  }
+}
+
+void PresencePushTask::FillMucStatus(const Jid& from, const XmlElement* stanza,
+                                     MucStatus* s) {
+  // First get the normal user status info. Happily, this is in the same
+  // format as it is for user presence.
+  FillStatus(from, stanza, s);
+
+  // Now look for src IDs, which will be present if this user is in a
+  // multiway call to this MUC.
+  const XmlElement* xstanza = stanza->FirstNamed(QN_MUC_USER_X);
+  if (xstanza) {
+    const XmlElement* media;
+    for (media = xstanza->FirstNamed(QN_GOOGLE_MUC_USER_MEDIA);
+        media; media = media->NextNamed(QN_GOOGLE_MUC_USER_MEDIA)) {
+
+      const XmlElement* type = media->FirstNamed(QN_GOOGLE_MUC_USER_TYPE);
+      if (!type) continue; // Shouldn't happen
+
+      const XmlElement* src_id = media->FirstNamed(QN_GOOGLE_MUC_USER_SRC_ID);
+      if (!src_id) continue; // Shouldn't happen
+
+      char *endptr;
+      uint32 src_id_num = strtoul(src_id->BodyText().c_str(), &endptr, 10);
+      if (src_id->BodyText().c_str()[0] == '\0' || endptr[0] != '\0') {
+        // String is not composed exclusively of leading whitespace plus a
+        // number (shouldn't happen). Ignore it.
+        continue;
+      }
+      // Else it's valid. Set it.
+
+      if (type->BodyText() == "audio") {
+        // This is the audio media element. Get the src-id.
+        s->set_audio_src_id(src_id_num);
+      } else if (type->BodyText() == "video") {
+        // This is the video media element. Get the src-id.
+        s->set_video_src_id(src_id_num);
+      }
+    }
+  }
+}
+
+}
diff --git a/talk/examples/call/presencepushtask.h b/talk/examples/call/presencepushtask.h
new file mode 100644
index 0000000..998a98e
--- /dev/null
+++ b/talk/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/xmpp/xmppengine.h"
+#include "talk/xmpp/xmpptask.h"
+#include "talk/base/sigslot.h"
+#include "talk/examples/call/status.h"
+#include "talk/examples/call/callclient.h"
+
+namespace buzz {
+
+class PresencePushTask : public XmppTask {
+ public:
+  PresencePushTask(Task * parent, CallClient* client)
+    : XmppTask(parent, XmppEngine::HL_TYPE),
+      client_(client) {}
+  virtual int ProcessStart();
+
+  sigslot::signal1<const Status&> SignalStatusUpdate;
+  sigslot::signal1<const Jid&> SignalMucJoined;
+  sigslot::signal2<const Jid&, int> SignalMucLeft;
+  sigslot::signal2<const Jid&, const MucStatus&> 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,
+                         Status* status);
+  static void FillMucStatus(const Jid& from, const XmlElement * stanza,
+                            MucStatus* status);
+
+ private:
+  CallClient* client_;
+};
+
+
+}
+
+#endif
diff --git a/talk/examples/call/status.h b/talk/examples/call/status.h
new file mode 100644
index 0000000..be4c2bd
--- /dev/null
+++ b/talk/examples/call/status.h
@@ -0,0 +1,245 @@
+/*
+ * 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 _STATUS_H_
+#define _STATUS_H_
+
+#include "talk/xmpp/jid.h"
+#include "talk/xmpp/constants.h"
+
+#define GOOGLE_CLIENT_NODE "http://www.google.com/xmpp/client/caps"
+
+namespace buzz {
+
+class Status {
+public:
+  Status() :
+    pri_(0),
+    show_(SHOW_NONE),
+    available_(false),
+    e_code_(0),
+    feedback_probation_(false),
+    know_capabilities_(false),
+    phone_capability_(false),
+    pmuc_capability_(false),
+    video_capability_(false),
+    camera_capability_(false),
+    is_google_client_(false) {}
+
+  ~Status() {}
+
+  // These are arranged in "priority order", i.e., if we see
+  // two statuses at the same priority but with different Shows,
+  // we will show the one with the highest show in the following
+  // order.
+  enum Show {
+    SHOW_NONE     = 0,
+    SHOW_OFFLINE  = 1,
+    SHOW_XA       = 2,
+    SHOW_AWAY     = 3,
+    SHOW_DND      = 4,
+    SHOW_ONLINE   = 5,
+    SHOW_CHAT     = 6,
+  };
+
+  const Jid & jid() const { return jid_; }
+  int priority() const { return pri_; }
+  Show show() const { return show_; }
+  const std::string & status() const { return status_; }
+  bool available() const { return available_ ; }
+  int error_code() const { return e_code_; }
+  const std::string & error_string() const { return e_str_; }
+  bool know_capabilities() const { return know_capabilities_; }
+  bool phone_capability() const { return phone_capability_; }
+  bool pmuc_capability() const { return pmuc_capability_; }
+  bool video_capability() const { return video_capability_; }
+  bool camera_capability() const { return camera_capability_; }
+  bool is_google_client() const { return is_google_client_; }
+  const std::string & version() const { return version_; }
+  bool feedback_probation() const { return feedback_probation_; }
+  const std::string& sent_time() const { return sent_time_; }
+
+  void set_jid(const Jid & jid) { jid_ = jid; }
+  void set_priority(int pri) { pri_ = pri; }
+  void set_show(Show show) { show_ = show; }
+  void set_status(const std::string & status) { status_ = status; }
+  void set_available(bool a) { available_ = a; }
+  void set_error(int e_code, const std::string e_str)
+      { e_code_ = e_code; e_str_ = e_str; }
+  void set_know_capabilities(bool f) { know_capabilities_ = f; }
+  void set_phone_capability(bool f) { phone_capability_ = f; }
+  void set_pmuc_capability(bool f) { pmuc_capability_ = f; }
+  void set_video_capability(bool f) { video_capability_ = f; }
+  void set_camera_capability(bool f) { camera_capability_ = f; }
+  void set_is_google_client(bool f) { is_google_client_ = f; }
+  void set_version(const std::string & v) { version_ = v; }
+  void set_feedback_probation(bool f) { feedback_probation_ = f; }
+  void set_sent_time(const std::string& time) { sent_time_ = time; }
+
+  void UpdateWith(const Status & new_value) {
+    if (!new_value.know_capabilities()) {
+       bool k = know_capabilities();
+       bool i = is_google_client();
+       bool p = phone_capability();
+       std::string v = version();
+
+       *this = new_value;
+
+       set_know_capabilities(k);
+       set_is_google_client(i);
+       set_phone_capability(p);
+       set_version(v);
+    }
+    else {
+      *this = new_value;
+    }
+  }
+
+  bool HasQuietStatus() const {
+    if (status_.empty())
+      return false;
+    return !(QuietStatus().empty());
+  }
+
+  // Knowledge of other clients' silly automatic status strings -
+  // Don't show these.
+  std::string QuietStatus() const {
+    if (jid_.resource().find("Psi") != std::string::npos) {
+      if (status_ == "Online" ||
+          status_.find("Auto Status") != std::string::npos)
+        return STR_EMPTY;
+    }
+    if (jid_.resource().find("Gaim") != std::string::npos) {
+      if (status_ == "Sorry, I ran out for a bit!")
+        return STR_EMPTY;
+    }
+    return TrimStatus(status_);
+  }
+
+  std::string ExplicitStatus() const {
+    std::string result = QuietStatus();
+    if (result.empty()) {
+      result = ShowStatus();
+    }
+    return result;
+  }
+
+  std::string ShowStatus() const {
+    std::string result;
+    if (!available()) {
+      result = "Offline";
+    }
+    else {
+      switch (show()) {
+        case SHOW_AWAY:
+        case SHOW_XA:
+          result = "Idle";
+          break;
+        case SHOW_DND:
+          result = "Busy";
+          break;
+        case SHOW_CHAT:
+          result = "Chatty";
+          break;
+        default:
+          result = "Available";
+          break;
+      }
+    }
+    return result;
+  }
+
+  static std::string TrimStatus(const std::string & st) {
+    std::string s(st);
+    int j = 0;
+    bool collapsing = true;
+    for (unsigned int i = 0; i < s.length(); i+= 1) {
+      if (s[i] <= ' ' && s[i] >= 0) {
+        if (collapsing) {
+          continue;
+        }
+        else {
+          s[j] = ' ';
+          j += 1;
+          collapsing = true;
+        }
+      }
+      else {
+        s[j] = s[i];
+        j += 1;
+        collapsing = false;
+      }
+    }
+    if (collapsing && j > 0) {
+      j -= 1;
+    }
+    s.erase(j, s.length());
+    return s;
+  }
+
+private:
+  Jid jid_;
+  int pri_;
+  Show show_;
+  std::string status_;
+  bool available_;
+  int e_code_;
+  std::string e_str_;
+  bool feedback_probation_;
+
+  // capabilities (valid only if know_capabilities_
+  bool know_capabilities_;
+  bool phone_capability_;
+  bool pmuc_capability_;
+  bool video_capability_;
+  bool camera_capability_;
+  bool is_google_client_;
+  std::string version_;
+
+  std::string sent_time_; // from the jabber:x:delay element
+};
+
+class MucStatus : public Status {
+public:
+  MucStatus() : audio_src_id_(0), video_src_id_(0) {}
+  uint32 audio_src_id() const { return audio_src_id_; }
+  uint32 video_src_id() const { return video_src_id_; }
+  void set_audio_src_id(uint32 audio_src_id) {
+    audio_src_id_ = audio_src_id;
+  }
+  void set_video_src_id(uint32 video_src_id) {
+    video_src_id_ = video_src_id;
+  }
+private:
+  uint32 audio_src_id_;
+  uint32 video_src_id_;
+};
+
+}
+
+
+#endif
diff --git a/talk/examples/call/voicemailjidrequester.cc b/talk/examples/call/voicemailjidrequester.cc
new file mode 100644
index 0000000..81f3dbc
--- /dev/null
+++ b/talk/examples/call/voicemailjidrequester.cc
@@ -0,0 +1,135 @@
+/*
+ * 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/discoitemsquerytask.h"
+#include "talk/examples/call/voicemailjidrequester.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/xmpp/constants.h"
+
+namespace buzz {
+
+VoicemailJidRequester::VoicemailJidRequester(talk_base::Task* parent,
+                                             const Jid& their_jid,
+                                             const Jid& my_jid) : Task(parent),
+                                             their_jid_(their_jid),
+                                             my_jid_(my_jid),
+                                             done_with_query_(false) {
+  parent_ = parent;
+}
+
+int VoicemailJidRequester::ProcessStart() {
+  // Start first query to node='voicemail'
+  DiscoItemsQueryTask* disco_items_task = new DiscoItemsQueryTask(this,
+      STR_VOICEMAIL, their_jid_.BareJid());
+  disco_items_task->SignalGotDiscoItems.connect(this,
+      &VoicemailJidRequester::OnFirstVoicemailJidSuccess);
+  disco_items_task->SignalDiscoItemsError.connect(this,
+      &VoicemailJidRequester::OnFirstVoicemailJidError);
+  disco_items_task->Start();
+  return STATE_BLOCKED;
+}
+
+void VoicemailJidRequester::OnFirstVoicemailJidError(buzz::Jid jid,
+    const XmlElement* xml_element) {
+  // First query gave us an error - try second query to node='outgoingvoicemail'
+  // and send it to your own jid
+  StartSecondQuery();
+}
+
+void VoicemailJidRequester::OnFirstVoicemailJidSuccess(buzz::Jid jid,
+    const XmlElement* xml_element) {
+  // Process the XML and fire the appropriate signals.  If the xml was valid,
+  // then we're done with queries.  If it wasn't valid, then start the second
+  // query.
+  bool valid_xml = ProcessVoicemailXml(xml_element);
+  if (valid_xml) {
+    done_with_query_ = true;
+    Wake();
+  } else {
+    StartSecondQuery();
+  }
+}
+
+void VoicemailJidRequester::OnSecondVoicemailJidError(buzz::Jid jid,
+    const XmlElement* xml_element) {
+  SignalVoicemailJidError(their_jid_);
+  done_with_query_ = true;
+  Wake();
+}
+
+void VoicemailJidRequester::OnSecondVoicemailJidSuccess(buzz::Jid jid,
+    const XmlElement* xml_element) {
+  // Whether this is good xml or bad, we're still done with the query
+  bool valid_xml = ProcessVoicemailXml(xml_element);
+  if (!valid_xml) {
+    SignalVoicemailJidError(their_jid_);
+  }
+  done_with_query_ = true;
+  Wake();
+}
+
+
+void VoicemailJidRequester::StartSecondQuery() {
+  // Send a query to your own jid to get the voicemail jid
+  DiscoItemsQueryTask* disco_items_task = new DiscoItemsQueryTask(this,
+      STR_OUTGOINGVOICEMAIL, my_jid_.BareJid());
+  disco_items_task->SignalGotDiscoItems.connect(this,
+      &VoicemailJidRequester::OnSecondVoicemailJidSuccess);
+  disco_items_task->SignalDiscoItemsError.connect(this,
+      &VoicemailJidRequester::OnSecondVoicemailJidError);
+  disco_items_task->Start();
+}
+
+int VoicemailJidRequester::Process(int state) {
+  if (done_with_query_) {
+    return STATE_DONE;
+  } else {
+    return talk_base::Task::Process(state);
+  }
+}
+
+bool VoicemailJidRequester::ProcessVoicemailXml(const XmlElement* xml_element) {
+  if (!xml_element) {
+    return false;
+  }
+  const std::string& node_name = xml_element->Attr(QN_NODE);
+  // Verify that it's one of the two nodes - we don't really care which one
+  if (node_name != "voicemail" &&
+      node_name != "outgoingvoicemail") {
+    return false;
+  }
+
+  const XmlElement* item = xml_element->FirstNamed(QN_DISCO_ITEM);
+  if (item) {
+    const std::string& jid_str = item->Attr(QN_JID);
+    buzz::Jid voicemail_jid(jid_str);
+    SignalGotVoicemailJid(their_jid_, voicemail_jid);
+    return true;
+  }
+  return false;
+}
+}
diff --git a/talk/examples/call/voicemailjidrequester.h b/talk/examples/call/voicemailjidrequester.h
new file mode 100644
index 0000000..34b3c4b
--- /dev/null
+++ b/talk/examples/call/voicemailjidrequester.h
@@ -0,0 +1,126 @@
+/*
+ * 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.
+ */
+
+// VoicemailJidRequester wraps the requesting of voicemail jids for a user.
+//
+// To request a voicemail jid, we first set off a query to the user's bare jid
+// that looks like this:
+//
+//      <iq type='get'
+//          from='foo@gmail.com/asdf'
+//          to='bar@google.com'
+//          id='1234'>
+//          <query xmlns=' http://jabber.org/protocol/disco#items'
+//                 node='voicemail '/>
+//      </iq>
+//
+// If foo@gmail.com's server supports voicemail, it'll return this, and forward
+// the jid up to phoneapp.  We do not do the second query.
+//
+//      <iq type='result'
+//          from='foo@google.com'
+//          to='bar@google.com/asdf'
+//          id='1234'>
+//          <query xmlns=' http://jabber.org/protocol/disco#items '
+//                 node=' voicemail '>
+//                 <item jid='bar@google.com/voicemail '/>
+//          </query>
+//      </iq>
+//
+// If we get an error, we spin off a new request:
+//
+//      <iq type='get'
+//          from='foo@google.com/asdf'
+//          to='foo@google.com'
+//          id='1234'>
+//          <query xmlns=' http://jabber.org/protocol/disco#items'
+//                 node='outgoingvoicemail '/>
+//      </iq>
+//
+// If both of these return errors, we then forward the request to phoneapp.
+
+#ifndef TALK_EXAMPLES_CALL_VOICEMAILJIDREQUESTER_H_
+#define TALK_EXAMPLES_CALL_VOICEMAILJIDREQUESTER_H_
+
+#include "talk/xmpp/xmpptask.h"
+
+namespace buzz {
+
+class Task;
+
+class VoicemailJidRequester : public sigslot::has_slots<>,
+                              public talk_base::Task {
+ public:
+  VoicemailJidRequester(talk_base::Task* parent, const Jid& their_jid, const Jid& my_jid);
+
+  // Provides the target jid and the voicemail to reach it
+  sigslot::signal2<const Jid&, const Jid&> SignalGotVoicemailJid;
+  sigslot::signal1<const Jid&> SignalVoicemailJidError;
+
+  virtual int ProcessStart();
+ protected:
+
+  virtual int Process(int state);
+
+ private:
+  // The first query (to node='voicemail' has returned an error) - we now spin
+  // off a request to node='outgoingvoicemail')
+  void OnFirstVoicemailJidError(buzz::Jid jid, const XmlElement* xml_element);
+
+  // The first query (to node='voicemail' has returned a successfully)
+  void OnFirstVoicemailJidSuccess(buzz::Jid jid, const XmlElement* xml_element);
+
+  // The second query (to node='outgoingvoicemail') has returned an error -
+  // nothing we can do now, just fire our error signal
+  void OnSecondVoicemailJidError(buzz::Jid jid, const XmlElement* xml_element);
+
+  // The second query (to node='outgoingvoicemail') has returned a successfully
+  void OnSecondVoicemailJidSuccess(buzz::Jid jid,
+                                   const XmlElement* xml_element);
+
+  // Parse the xml, fire SignalGotVoicemail jid if it was valid (and had a jid)
+  // and return true if it was a valid xml.
+  bool ProcessVoicemailXml(const XmlElement* xml_element);
+
+  // Send a query to your own jid to get the voicemail jid.  This is used after
+  // the first query fails.
+  void StartSecondQuery();
+
+  talk_base::Task* parent_;
+
+  Jid their_jid_;
+
+  // Your own jid (not the other user's)
+  Jid my_jid_;
+
+  // A flag indicating whether or not we're done with the query so that we can
+  // set the state correctly in Process(int state)
+  bool done_with_query_;
+};
+}
+
+#endif  // TALK_EXAMPLES_CALL_VOICEMAILJIDREQUESTER_H_
diff --git a/talk/examples/login/login_main.cc b/talk/examples/login/login_main.cc
new file mode 100644
index 0000000..186020a
--- /dev/null
+++ b/talk/examples/login/login_main.cc
@@ -0,0 +1,64 @@
+/*
+ * 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 <cstdio>
+#include <iostream>
+
+#include "talk/base/thread.h"
+#include "talk/xmpp/xmppclientsettings.h"
+#include "talk/examples/login/xmppthread.h"
+
+int main(int argc, char **argv) {
+  std::cout << "Auth Cookie: ";
+  std::string auth_cookie;
+  std::getline(std::cin, auth_cookie);
+
+  std::cout << "User Name: ";
+  std::string username;
+  std::getline(std::cin, username);
+
+  // Start xmpp on a different thread
+  XmppThread thread;
+  thread.Start();
+
+  buzz::XmppClientSettings xcs;
+  xcs.set_user(username.c_str());
+  xcs.set_host("gmail.com");
+  xcs.set_use_tls(false);
+  xcs.set_auth_cookie(auth_cookie.c_str());
+  xcs.set_server(talk_base::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/talk/examples/login/xmppauth.cc b/talk/examples/login/xmppauth.cc
new file mode 100644
index 0000000..3551b97
--- /dev/null
+++ b/talk/examples/login/xmppauth.cc
@@ -0,0 +1,87 @@
+/*
+ * 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/login/xmppauth.h"
+
+#include <algorithm>
+
+#include "talk/xmpp/saslcookiemechanism.h"
+#include "talk/xmpp/saslplainmechanism.h"
+
+XmppAuth::XmppAuth() : done_(false) {
+}
+
+XmppAuth::~XmppAuth() {
+}
+  
+void XmppAuth::StartPreXmppAuth(const buzz::Jid & jid,
+                                const talk_base::SocketAddress & server,
+                                const talk_base::CryptString & pass,
+                                const std::string & auth_cookie) {
+  jid_ = jid;
+  passwd_ = pass;
+  auth_cookie_ = auth_cookie;
+  done_ = true;
+
+  SignalAuthDone();
+}
+  
+std::string XmppAuth::ChooseBestSaslMechanism(
+    const std::vector<std::string> & mechanisms,
+    bool encrypted) {
+  std::vector<std::string>::const_iterator it;
+
+  // a token is the weakest auth - 15s, service-limited, so prefer it.
+  it = std::find(mechanisms.begin(), mechanisms.end(), "X-GOOGLE-TOKEN");
+  if (it != mechanisms.end() && !auth_cookie_.empty())
+    return "X-GOOGLE-TOKEN";
+
+  // a cookie is the next weakest - 14 days
+  it = std::find(mechanisms.begin(), mechanisms.end(), "X-GOOGLE-COOKIE");
+  if (it != mechanisms.end() && !auth_cookie_.empty())
+    return "X-GOOGLE-COOKIE";
+
+    it = std::find(mechanisms.begin(), mechanisms.end(), "PLAIN");
+    if (it != mechanisms.end())
+      return "PLAIN";
+
+  // No good mechanism found
+ return "";
+}
+
+buzz::SaslMechanism* XmppAuth::CreateSaslMechanism(
+    const std::string & mechanism) {
+  if (mechanism == "X-GOOGLE-TOKEN") {
+    return new buzz::SaslCookieMechanism(mechanism, jid_.Str(), auth_cookie_);
+  //} else if (mechanism == "X-GOOGLE-COOKIE") {
+  //  return new buzz::SaslCookieMechanism(mechanism, jid.Str(), sid_);
+  } else if (mechanism == "PLAIN") {
+    return new buzz::SaslPlainMechanism(jid_, passwd_);
+  } else {
+    return NULL;
+  }
+}
diff --git a/talk/examples/login/xmppauth.h b/talk/examples/login/xmppauth.h
new file mode 100644
index 0000000..18672b8
--- /dev/null
+++ b/talk/examples/login/xmppauth.h
@@ -0,0 +1,72 @@
+/*
+ * 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 _XMPPAUTH_H_
+#define _XMPPAUTH_H_
+
+#include <vector>
+
+#include "talk/base/cryptstring.h"
+#include "talk/base/sigslot.h"
+#include "talk/xmpp/jid.h"
+#include "talk/xmpp/saslhandler.h"
+#include "talk/xmpp/prexmppauth.h"
+
+class XmppAuth: public buzz::PreXmppAuth {
+public:
+  XmppAuth();
+  virtual ~XmppAuth();
+
+  virtual void StartPreXmppAuth(const buzz::Jid & jid,
+                                const talk_base::SocketAddress & server,
+                                const talk_base::CryptString & pass,
+                                const std::string & auth_cookie);
+
+  virtual bool IsAuthDone() const { return done_; }
+  virtual bool IsAuthorized() const { return true; }
+  virtual bool HadError() const { return false; }
+  virtual int  GetError() const { return 0; }
+  virtual buzz::CaptchaChallenge GetCaptchaChallenge() const {
+      return buzz::CaptchaChallenge();
+  }
+  virtual std::string GetAuthCookie() const { return auth_cookie_; }
+
+  virtual std::string ChooseBestSaslMechanism(
+      const std::vector<std::string> & mechanisms,
+      bool encrypted);
+
+  virtual buzz::SaslMechanism * CreateSaslMechanism(
+      const std::string & mechanism);
+
+private:
+  buzz::Jid jid_;
+  talk_base::CryptString passwd_;
+  std::string auth_cookie_;
+  bool done_;
+};
+
+#endif
diff --git a/talk/examples/login/xmpppump.cc b/talk/examples/login/xmpppump.cc
new file mode 100644
index 0000000..de4057b
--- /dev/null
+++ b/talk/examples/login/xmpppump.cc
@@ -0,0 +1,78 @@
+/*
+ * 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/login/xmpppump.h"
+#include "talk/examples/login/xmppauth.h"
+
+XmppPump::XmppPump(XmppPumpNotify * notify) {
+  state_ = buzz::XmppEngine::STATE_NONE;
+  notify_ = notify;
+  client_ = new buzz::XmppClient(this);  // NOTE: deleted by TaskRunner
+}
+
+void XmppPump::DoLogin(const buzz::XmppClientSettings & xcs,
+                       buzz::AsyncSocket* socket,
+                       buzz::PreXmppAuth* auth) {
+  OnStateChange(buzz::XmppEngine::STATE_START);
+  if (!AllChildrenDone()) {
+    client_->SignalStateChange.connect(this, &XmppPump::OnStateChange);
+    client_->Connect(xcs, "", socket, auth);
+    client_->Start();
+  }
+}
+
+void XmppPump::DoDisconnect() {
+  if (!AllChildrenDone())
+    client_->Disconnect();
+  OnStateChange(buzz::XmppEngine::STATE_CLOSED);
+}
+
+void XmppPump::OnStateChange(buzz::XmppEngine::State state) {
+  if (state_ == state)
+    return;
+  state_ = state;
+  if (notify_ != NULL)
+    notify_->OnStateChange(state);
+}
+
+void XmppPump::WakeTasks() {
+  talk_base::Thread::Current()->Post(this);
+}
+
+int64 XmppPump::CurrentTime() {
+  return (int64)talk_base::Time();
+}
+
+void XmppPump::OnMessage(talk_base::Message *pmsg) {
+  RunTasks();
+}
+
+buzz::XmppReturnStatus XmppPump::SendStanza(const buzz::XmlElement *stanza) {
+  if (!AllChildrenDone())
+    return client_->SendStanza(stanza);
+  return buzz::XMPP_RETURN_BADSTATE;
+}
diff --git a/talk/examples/login/xmpppump.h b/talk/examples/login/xmpppump.h
new file mode 100644
index 0000000..4e79748
--- /dev/null
+++ b/talk/examples/login/xmpppump.h
@@ -0,0 +1,74 @@
+/*
+ * 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 _XMPPPUMP_H_
+#define _XMPPPUMP_H_
+
+#include "talk/base/messagequeue.h"
+#include "talk/base/taskrunner.h"
+#include "talk/base/thread.h"
+#include "talk/base/time.h"
+#include "talk/xmpp/xmppclient.h"
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/xmpptask.h"
+
+// Simple xmpp pump
+
+class XmppPumpNotify {
+public:
+  virtual ~XmppPumpNotify() {}
+  virtual void OnStateChange(buzz::XmppEngine::State state) = 0;
+};
+
+class XmppPump : public talk_base::MessageHandler, public talk_base::TaskRunner {
+public:
+  XmppPump(XmppPumpNotify * notify = NULL);
+
+  buzz::XmppClient *client() { return client_; }
+
+  void DoLogin(const buzz::XmppClientSettings & xcs,
+               buzz::AsyncSocket* socket,
+               buzz::PreXmppAuth* auth);
+  void DoDisconnect();
+
+  void OnStateChange(buzz::XmppEngine::State state);
+
+  void WakeTasks();
+
+  int64 CurrentTime();
+
+  void OnMessage(talk_base::Message *pmsg);
+
+  buzz::XmppReturnStatus SendStanza(const buzz::XmlElement *stanza);
+
+private:
+  buzz::XmppClient *client_;
+  buzz::XmppEngine::State state_;
+  XmppPumpNotify *notify_;
+};
+
+#endif // _XMPPPUMP_H_
diff --git a/talk/examples/login/xmppsocket.cc b/talk/examples/login/xmppsocket.cc
new file mode 100644
index 0000000..89e8662
--- /dev/null
+++ b/talk/examples/login/xmppsocket.cc
@@ -0,0 +1,249 @@
+/*
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include "talk/base/basicdefs.h"
+#include "talk/base/logging.h"
+#include "talk/base/thread.h"
+#ifdef FEATURE_ENABLE_SSL
+#include "talk/base/ssladapter.h"
+#endif
+#include "xmppsocket.h"
+
+#ifdef USE_SSLSTREAM
+#include "talk/base/socketstream.h"
+#ifdef FEATURE_ENABLE_SSL
+#include "talk/base/sslstreamadapter.h"
+#endif  // FEATURE_ENABLE_SSL
+#endif  // USE_SSLSTREAM
+
+XmppSocket::XmppSocket(bool tls) : tls_(tls) {
+  talk_base::Thread* pth = talk_base::Thread::Current();
+  talk_base::AsyncSocket* socket =
+    pth->socketserver()->CreateAsyncSocket(SOCK_STREAM);
+#ifndef USE_SSLSTREAM
+#ifdef FEATURE_ENABLE_SSL
+  if (tls_) {
+    socket = talk_base::SSLAdapter::Create(socket);
+  }
+#endif  // FEATURE_ENABLE_SSL
+  cricket_socket_ = socket;
+  cricket_socket_->SignalReadEvent.connect(this, &XmppSocket::OnReadEvent);
+  cricket_socket_->SignalWriteEvent.connect(this, &XmppSocket::OnWriteEvent);
+  cricket_socket_->SignalConnectEvent.connect(this,
+                                              &XmppSocket::OnConnectEvent);
+  cricket_socket_->SignalCloseEvent.connect(this, &XmppSocket::OnCloseEvent);
+#else  // USE_SSLSTREAM
+  cricket_socket_ = socket;
+  stream_ = new talk_base::SocketStream(cricket_socket_);
+#ifdef FEATURE_ENABLE_SSL
+  if (tls_)
+    stream_ = talk_base::SSLStreamAdapter::Create(stream_);
+#endif  // FEATURE_ENABLE_SSL
+  stream_->SignalEvent.connect(this, &XmppSocket::OnEvent);
+#endif  // USE_SSLSTREAM
+
+  state_ = buzz::AsyncSocket::STATE_CLOSED;
+}
+
+XmppSocket::~XmppSocket() {
+  Close();
+#ifndef USE_SSLSTREAM
+  delete cricket_socket_;
+#else  // USE_SSLSTREAM
+  delete stream_;
+#endif  // USE_SSLSTREAM
+}
+
+#ifndef USE_SSLSTREAM
+void XmppSocket::OnReadEvent(talk_base::AsyncSocket * socket) {
+  SignalRead();
+}
+
+void XmppSocket::OnWriteEvent(talk_base::AsyncSocket * socket) {
+  // Write bytes if there are any
+  while (buffer_.Length() != 0) {
+    int written = cricket_socket_->Send(buffer_.Data(), buffer_.Length());
+    if (written > 0) {
+      buffer_.Shift(written);
+      continue;
+    }
+    if (!cricket_socket_->IsBlocking())
+      LOG(LS_ERROR) << "Send error: " << cricket_socket_->GetError();
+    return;
+  }
+}
+
+void XmppSocket::OnConnectEvent(talk_base::AsyncSocket * socket) {
+#if defined(FEATURE_ENABLE_SSL)
+  if (state_ == buzz::AsyncSocket::STATE_TLS_CONNECTING) {
+    state_ = buzz::AsyncSocket::STATE_TLS_OPEN;
+    SignalSSLConnected();
+    OnWriteEvent(cricket_socket_);
+    return;
+  }
+#endif  // !defined(FEATURE_ENABLE_SSL)
+  state_ = buzz::AsyncSocket::STATE_OPEN;
+  SignalConnected();
+}
+
+void XmppSocket::OnCloseEvent(talk_base::AsyncSocket * socket, int error) {
+  SignalCloseEvent(error);
+}
+
+#else  // USE_SSLSTREAM
+
+void XmppSocket::OnEvent(talk_base::StreamInterface* stream,
+                         int events, int err) {
+  if ((events & talk_base::SE_OPEN)) {
+#if defined(FEATURE_ENABLE_SSL)
+    if (state_ == buzz::AsyncSocket::STATE_TLS_CONNECTING) {
+      state_ = buzz::AsyncSocket::STATE_TLS_OPEN;
+      SignalSSLConnected();
+      events |= talk_base::SE_WRITE;
+    } else
+#endif
+    {
+      state_ = buzz::AsyncSocket::STATE_OPEN;
+      SignalConnected();
+    }
+  }
+  if ((events & talk_base::SE_READ))
+    SignalRead();
+  if ((events & talk_base::SE_WRITE)) {
+    // Write bytes if there are any
+    while (buffer_.Length() != 0) {
+      talk_base::StreamResult result;
+      size_t written;
+      int error;
+      result = stream_->Write(buffer_.Data(), buffer_.Length(),
+                              &written, &error);
+      if (result == talk_base::SR_ERROR) {
+        LOG(LS_ERROR) << "Send error: " << error;
+        return;
+      }
+      if (result == talk_base::SR_BLOCK)
+        return;
+      ASSERT(result == talk_base::SR_SUCCESS);
+      ASSERT(written > 0);
+      buffer_.Shift(written);
+    }
+  }
+  if ((events & talk_base::SE_CLOSE))
+    SignalCloseEvent(err);
+}
+#endif  // USE_SSLSTREAM
+
+buzz::AsyncSocket::State XmppSocket::state() {
+  return state_;
+}
+
+buzz::AsyncSocket::Error XmppSocket::error() {
+  return buzz::AsyncSocket::ERROR_NONE;
+}
+
+int XmppSocket::GetError() {
+  return 0;
+}
+
+bool XmppSocket::Connect(const talk_base::SocketAddress& addr) {
+  if (cricket_socket_->Connect(addr) < 0) {
+    return cricket_socket_->IsBlocking();
+  }
+  return true;
+}
+
+bool XmppSocket::Read(char * data, size_t len, size_t* len_read) {
+#ifndef USE_SSLSTREAM
+  int read = cricket_socket_->Recv(data, len);
+  if (read > 0) {
+    *len_read = (size_t)read;
+    return true;
+  }
+#else  // USE_SSLSTREAM
+  talk_base::StreamResult result = stream_->Read(data, len, len_read, NULL);
+  if (result == talk_base::SR_SUCCESS)
+    return true;
+#endif  // USE_SSLSTREAM
+  return false;
+}
+
+bool XmppSocket::Write(const char * data, size_t len) {
+  buffer_.WriteBytes(data, len);
+#ifndef USE_SSLSTREAM
+  OnWriteEvent(cricket_socket_);
+#else  // USE_SSLSTREAM
+  OnEvent(stream_, talk_base::SE_WRITE, 0);
+#endif  // USE_SSLSTREAM
+  return true;
+}
+
+bool XmppSocket::Close() {
+  if (state_ != buzz::AsyncSocket::STATE_OPEN)
+    return false;
+#ifndef USE_SSLSTREAM
+  if (cricket_socket_->Close() == 0) {
+    state_ = buzz::AsyncSocket::STATE_CLOSED;
+    SignalClosed();
+    return true;
+  }
+  return false;
+#else  // USE_SSLSTREAM
+  state_ = buzz::AsyncSocket::STATE_CLOSED;
+  stream_->Close();
+  SignalClosed();
+  return true;
+#endif  // USE_SSLSTREAM
+}
+
+bool XmppSocket::StartTls(const std::string & domainname) {
+#if defined(FEATURE_ENABLE_SSL)
+  if (!tls_)
+    return false;
+#ifndef USE_SSLSTREAM
+  talk_base::SSLAdapter* ssl_adapter =
+    static_cast<talk_base::SSLAdapter *>(cricket_socket_);
+  ssl_adapter->set_ignore_bad_cert(true);
+  if (ssl_adapter->StartSSL(domainname.c_str(), false) != 0)
+    return false;
+#else  // USE_SSLSTREAM
+  talk_base::SSLStreamAdapter* ssl_stream =
+    static_cast<talk_base::SSLStreamAdapter *>(stream_);
+  ssl_stream->set_ignore_bad_cert(true);
+  if (ssl_stream->StartSSLWithServer(domainname.c_str()) != 0)
+    return false;
+#endif  // USE_SSLSTREAM
+  state_ = buzz::AsyncSocket::STATE_TLS_CONNECTING;
+  return true;
+#else  // !defined(FEATURE_ENABLE_SSL)
+  return false;
+#endif  // !defined(FEATURE_ENABLE_SSL)
+}
diff --git a/talk/examples/login/xmppsocket.h b/talk/examples/login/xmppsocket.h
new file mode 100644
index 0000000..bce8ee3
--- /dev/null
+++ b/talk/examples/login/xmppsocket.h
@@ -0,0 +1,81 @@
+/*
+ * 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 _XMPPSOCKET_H_
+#define _XMPPSOCKET_H_
+
+#include "talk/base/asyncsocket.h"
+#include "talk/base/bytebuffer.h"
+#include "talk/base/sigslot.h"
+#include "talk/xmpp/asyncsocket.h"
+
+// The below define selects the SSLStreamAdapter implementation for
+// SSL, as opposed to the SSLAdapter socket adapter.
+// #define USE_SSLSTREAM 
+
+namespace talk_base {
+  class StreamInterface;
+};
+extern talk_base::AsyncSocket* cricket_socket_;
+
+class XmppSocket : public buzz::AsyncSocket, public sigslot::has_slots<> {
+public:
+  XmppSocket(bool tls);
+  ~XmppSocket();
+
+  virtual buzz::AsyncSocket::State state();
+  virtual buzz::AsyncSocket::Error error();
+  virtual int GetError();
+
+  virtual bool Connect(const talk_base::SocketAddress& addr);
+  virtual bool Read(char * data, size_t len, size_t* len_read);
+  virtual bool Write(const char * data, size_t len);
+  virtual bool Close();
+  virtual bool StartTls(const std::string & domainname);
+
+  sigslot::signal1<int> SignalCloseEvent;
+
+private:
+#ifndef USE_SSLSTREAM
+  void OnReadEvent(talk_base::AsyncSocket * socket);
+  void OnWriteEvent(talk_base::AsyncSocket * socket);
+  void OnConnectEvent(talk_base::AsyncSocket * socket);
+  void OnCloseEvent(talk_base::AsyncSocket * socket, int error);
+#else  // USE_SSLSTREAM
+  void OnEvent(talk_base::StreamInterface* stream, int events, int err);
+#endif  // USE_SSLSTREAM
+
+  talk_base::AsyncSocket * cricket_socket_;
+#ifdef USE_SSLSTREAM
+  talk_base::StreamInterface *stream_;
+#endif  // USE_SSLSTREAM
+  buzz::AsyncSocket::State state_;
+  talk_base::ByteBuffer buffer_;
+  bool tls_;
+};
+
+#endif // _XMPPSOCKET_H_
diff --git a/talk/examples/login/xmppthread.cc b/talk/examples/login/xmppthread.cc
new file mode 100644
index 0000000..c030391
--- /dev/null
+++ b/talk/examples/login/xmppthread.cc
@@ -0,0 +1,80 @@
+/*
+ * 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/xmpp/xmppclientsettings.h"
+#include "talk/examples/login/xmppthread.h"
+#include "talk/examples/login/xmppauth.h"
+
+namespace {
+
+const uint32 MSG_LOGIN = 1;
+const uint32 MSG_DISCONNECT = 2;
+
+struct LoginData: public talk_base::MessageData {
+  LoginData(const buzz::XmppClientSettings& s) : xcs(s) {}
+  virtual ~LoginData() {}
+
+  buzz::XmppClientSettings xcs;
+};
+
+} // namespace
+
+XmppThread::XmppThread() {
+  pump_ = new XmppPump(this);
+}
+
+XmppThread::~XmppThread() {
+  delete pump_;
+}
+
+void XmppThread::ProcessMessages(int cms) {
+  talk_base::Thread::ProcessMessages(cms);
+}
+
+void XmppThread::Login(const buzz::XmppClientSettings& xcs) {
+  Post(this, MSG_LOGIN, new LoginData(xcs));
+}
+
+void XmppThread::Disconnect() {
+  Post(this, MSG_DISCONNECT);
+}
+
+void XmppThread::OnStateChange(buzz::XmppEngine::State state) {
+}
+
+void XmppThread::OnMessage(talk_base::Message* pmsg) {
+  if (pmsg->message_id == MSG_LOGIN) {
+    ASSERT(pmsg->pdata != NULL);
+    LoginData* data = reinterpret_cast<LoginData*>(pmsg->pdata);
+    pump_->DoLogin(data->xcs, new XmppSocket(false), new XmppAuth());
+    delete data;
+  } else if (pmsg->message_id == MSG_DISCONNECT) {
+    pump_->DoDisconnect();
+  } else {
+    ASSERT(false);
+  }
+}
diff --git a/talk/examples/login/xmppthread.h b/talk/examples/login/xmppthread.h
new file mode 100644
index 0000000..247b7bd
--- /dev/null
+++ b/talk/examples/login/xmppthread.h
@@ -0,0 +1,57 @@
+/*
+ * 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 _XMPPTHREAD_H_
+#define _XMPPTHREAD_H_
+
+#include "talk/xmpp/xmppclientsettings.h"
+#include "talk/base/thread.h"
+#include "talk/examples/login/xmpppump.h"
+#include "talk/examples/login/xmppsocket.h"
+#include <iostream>
+
+class XmppThread:
+    public talk_base::Thread, XmppPumpNotify, talk_base::MessageHandler {
+public:
+  XmppThread();
+  ~XmppThread();
+
+  buzz::XmppClient* client() { return pump_->client(); }
+
+  void ProcessMessages(int cms);
+
+  void Login(const buzz::XmppClientSettings & xcs);
+  void Disconnect();
+
+private:
+  XmppPump* pump_;
+
+  void OnStateChange(buzz::XmppEngine::State state);
+  void OnMessage(talk_base::Message* pmsg);
+};
+
+#endif // _XMPPTHREAD_H_
diff --git a/talk/libjingle.scons b/talk/libjingle.scons
new file mode 100644
index 0000000..870a5d8
--- /dev/null
+++ b/talk/libjingle.scons
@@ -0,0 +1,313 @@
+import talk
+Import("env")
+
+talk.Library(env, name = "expat",
+             cppdefines = [
+               "XML_STATIC",
+             ],
+             srcs = [
+               "third_party/expat-2.0.1/lib/xmlparse.c",
+               "third_party/expat-2.0.1/lib/xmlrole.c",
+               "third_party/expat-2.0.1/lib/xmltok.c",
+             ],
+             includedirs = [
+               "third_party/expat-2.0.1/",
+             ],
+             win_cppdefines = [
+               "COMPILED_FROM_DSP",
+             ],
+             posix_cppdefines = [
+               "HAVE_EXPAT_CONFIG_H",
+             ],
+)
+talk.Library(env, name = "libsrtp",
+             srcs = [
+               "third_party/srtp/crypto/cipher/aes.c",
+               "third_party/srtp/crypto/cipher/aes_cbc.c",
+               "third_party/srtp/crypto/cipher/aes_icm.c",
+               "third_party/srtp/crypto/cipher/cipher.c",
+               "third_party/srtp/crypto/cipher/null_cipher.c",
+               "third_party/srtp/crypto/hash/auth.c",
+               "third_party/srtp/crypto/hash/hmac.c",
+               "third_party/srtp/crypto/hash/null_auth.c",
+               "third_party/srtp/crypto/hash/sha1.c",
+               "third_party/srtp/crypto/replay/rdb.c",
+               "third_party/srtp/crypto/replay/rdbx.c",
+               "third_party/srtp/crypto/replay/ut_sim.c",
+               "third_party/srtp/crypto/math/datatypes.c",
+               "third_party/srtp/crypto/math/stat.c",
+               "third_party/srtp/crypto/kernel/alloc.c",
+               "third_party/srtp/crypto/kernel/crypto_kernel.c",
+               "third_party/srtp/crypto/kernel/err.c",
+               "third_party/srtp/crypto/kernel/key.c",
+               "third_party/srtp/crypto/rng/ctr_prng.c",
+               "third_party/srtp/crypto/rng/rand_source.c",
+               "third_party/srtp/srtp/ekt.c",
+               "third_party/srtp/srtp/srtp.c",
+             ],
+             includedirs = [
+               "third_party/srtp/include",
+               "third_party/srtp/crypto/include",
+             ],
+             win_ccflags = [
+               "/wd4701",
+               "/wd4702",
+             ],
+)
+talk.Library(env, name = "libjingle",
+             lin_srcs = [
+               "base/latebindingsymboltable.cc",
+               "base/linux.cc",
+               "session/phone/libudevsymboltable.cc",
+               "session/phone/v4llookup.cc",
+             ],
+             mac_srcs = [
+               "base/macconversion.cc",
+               "base/macutils.cc",
+               "session/phone/devicemanager_mac.mm",
+             ],
+             posix_srcs = [
+               "base/unixfilesystem.cc",
+               "base/opensslidentity.cc",
+               "base/opensslstreamadapter.cc",
+               "base/sslidentity.cc",
+               "base/sslstreamadapter.cc",
+             ],
+             cppdefines = [
+               "FEATURE_ENABLE_VOICEMAIL",
+               "EXPAT_RELATIVE_PATH",
+               "SRTP_RELATIVE_PATH",
+               "XML_STATIC",
+             ],
+             srcs = [
+               "base/asyncfile.cc",
+               "base/asynchttprequest.cc",
+               "base/asyncsocket.cc",
+               "base/asynctcpsocket.cc",
+               "base/asyncudpsocket.cc",
+               "base/autodetectproxy.cc",
+               "base/base64.cc",
+               "base/basicpacketsocketfactory.cc",
+               "base/bytebuffer.cc",
+               "base/checks.cc",
+               "base/common.cc",
+               "base/diskcache.cc",
+               "base/event.cc",
+               "base/fileutils.cc",
+               "base/firewallsocketserver.cc",
+               "base/flags.cc",
+               "base/helpers.cc",
+               "base/host.cc",
+               "base/httpbase.cc",
+               "base/httpclient.cc",
+               "base/httpcommon.cc",
+               "base/httprequest.cc",
+               "base/logging.cc",
+               "base/md5c.c",
+               "base/messagehandler.cc",
+               "base/messagequeue.cc",
+               "base/nethelpers.cc",
+               "base/network.cc",
+               "base/openssladapter.cc",
+               "base/pathutils.cc",
+               "base/physicalsocketserver.cc",
+               "base/proxydetect.cc",
+               "base/proxyinfo.cc",
+               "base/ratetracker.cc",
+               "base/signalthread.cc",
+               "base/socketadapters.cc",
+               "base/socketaddress.cc",
+               "base/socketaddresspair.cc",
+               "base/socketpool.cc",
+               "base/socketstream.cc",
+               "base/ssladapter.cc",
+               "base/sslsocketfactory.cc",
+               "base/stream.cc",
+               "base/stringdigest.cc",
+               "base/stringencode.cc",
+               "base/stringutils.cc",
+               "base/task.cc",
+               "base/taskparent.cc",
+               "base/taskrunner.cc",
+               "base/thread.cc",
+               "base/time.cc",
+               "base/urlencode.cc",
+               "p2p/base/constants.cc",
+               "p2p/base/p2ptransport.cc",
+               "p2p/base/p2ptransportchannel.cc",
+               "p2p/base/parsing.cc",
+               "p2p/base/port.cc",
+               "p2p/base/pseudotcp.cc",
+               "p2p/base/relayport.cc",
+               "p2p/base/relayserver.cc",
+               "p2p/base/rawtransport.cc",
+               "p2p/base/rawtransportchannel.cc",
+               "p2p/base/session.cc",
+               "p2p/base/sessiondescription.cc",
+               "p2p/base/sessionmanager.cc",
+               "p2p/base/sessionmessages.cc",
+               "p2p/base/stun.cc",
+               "p2p/base/stunport.cc",
+               "p2p/base/stunrequest.cc",
+               "p2p/base/stunserver.cc",
+               "p2p/base/tcpport.cc",
+               "p2p/base/transport.cc",
+               "p2p/base/transportchannel.cc",
+               "p2p/base/transportchannelproxy.cc",
+               "p2p/base/udpport.cc",
+               "p2p/client/basicportallocator.cc",
+               "p2p/client/httpportallocator.cc",
+               "p2p/client/socketmonitor.cc",
+               "session/tunnel/pseudotcpchannel.cc",
+               "session/tunnel/tunnelsessionclient.cc",
+               "session/tunnel/securetunnelsessionclient.cc",
+               "session/phone/audiomonitor.cc",
+               "session/phone/call.cc",
+               "session/phone/channel.cc",
+               "session/phone/channelmanager.cc",
+               "session/phone/codec.cc",
+               "session/phone/devicemanager.cc",
+               "session/phone/filemediaengine.cc",
+               "session/phone/mediaengine.cc",
+               "session/phone/mediamonitor.cc",
+               "session/phone/mediasessionclient.cc",
+               "session/phone/rtpdump.cc",
+               "session/phone/rtcpmuxfilter.cc",
+               "session/phone/soundclip.cc",
+               "session/phone/srtpfilter.cc",
+               "xmllite/qname.cc",
+               "xmllite/xmlbuilder.cc",
+               "xmllite/xmlconstants.cc",
+               "xmllite/xmlelement.cc",
+               "xmllite/xmlnsstack.cc",
+               "xmllite/xmlparser.cc",
+               "xmllite/xmlprinter.cc",
+               "xmpp/constants.cc",
+               "xmpp/jid.cc",
+               "xmpp/ratelimitmanager.cc",
+               "xmpp/saslmechanism.cc",
+               "xmpp/xmppclient.cc",
+               "xmpp/xmppengineimpl.cc",
+               "xmpp/xmppengineimpl_iq.cc",
+               "xmpp/xmpplogintask.cc",
+               "xmpp/xmppstanzaparser.cc",
+               "xmpp/xmpptask.cc",
+             ],
+             includedirs = [
+               "third_party/libudev",
+               "third_party/expat-2.0.1/",
+               "third_party/srtp/include",
+               "third_party/srtp/crypto/include",
+             ],
+             win_srcs = [
+               "base/schanneladapter.cc",
+               "base/win32.cc",
+               "base/win32filesystem.cc",
+               "base/win32securityerrors.cc",
+               "base/win32socketserver.cc",
+               "base/win32socketinit.cc",
+               "base/win32window.cc",
+               "base/winfirewall.cc",
+               "base/winping.cc",
+             ],
+)
+talk.App(env, name = "login",
+         libs = [
+           "libjingle",
+           "expat",
+           "libxmpphelp",
+         ],
+         srcs = [
+           "examples/login/xmppthread.cc",
+           "examples/login/login_main.cc",
+         ],
+         mac_libs = [
+           "crypto",
+           "ssl",
+         ],
+         lin_libs = [
+           "libpthread",
+           ":libssl.so.0.9.8",
+         ],
+)
+talk.Library(env, name = "libxmpphelp",
+             libs = [
+               "libjingle",
+             ],
+             srcs = [
+               "examples/login/xmppauth.cc",
+               "examples/login/xmpppump.cc",
+               "examples/login/xmppsocket.cc",
+             ],
+)
+talk.App(env, name = "call",
+         mac_frameworks = [
+           "AudioToolbox",
+           "AudioUnit",
+           "Cocoa",
+           "CoreAudio",
+           "CoreFoundation",
+           "IOKit",
+           "QTKit",
+           "QuickTime",
+         ],
+         win_libs = [
+           "d3d9.lib",
+           "gdi32.lib",
+           "powrprof.lib",
+           "strmiids.lib",
+           "winmm.lib",
+         ],
+         mac_libs = [
+           "crypto",
+           "ssl",
+         ],
+         cppdefines = [
+           "FEATURE_ENABLE_VOICEMAIL",
+         ],
+         lin_libs = [
+           "libasound",
+           "libpthread",
+           ":libssl.so.0.9.8",
+         ],
+         srcs = [
+           "examples/call/call_main.cc",
+           "examples/call/callclient.cc",
+           "examples/call/console.cc",
+           "examples/call/discoitemsquerytask.cc",
+           "examples/call/friendinvitesendtask.cc",
+           "examples/call/mucinviterecvtask.cc",
+           "examples/call/mucinvitesendtask.cc",
+           "examples/call/presenceouttask.cc",
+           "examples/call/presencepushtask.cc",
+           "examples/call/voicemailjidrequester.cc",
+         ],
+         libs = [
+           "libjingle",
+           "expat",
+           "libsrtp",
+           "libxmpphelp",
+         ],
+)
+talk.App(env, name = "relayserver",
+         libs = [
+           "libjingle",
+         ],
+         srcs = [
+           "p2p/base/relayserver_main.cc",
+         ],
+         lin_libs = [
+           "libpthread",
+         ],
+)
+talk.App(env, name = "stunserver",
+         libs = [
+           "libjingle",
+         ],
+         srcs = [
+           "p2p/base/stunserver_main.cc",
+         ],
+         lin_libs = [
+           "libpthread",
+         ],
+)
diff --git a/talk/main.scons b/talk/main.scons
new file mode 100644
index 0000000..b8705d1
--- /dev/null
+++ b/talk/main.scons
@@ -0,0 +1,496 @@
+# -*- Python -*-
+#
+# All the helper functions are defined in:
+#  - site_scons/talk.py
+# Use 'import talk' in any .scons file to get access to it.
+# Add any new helper functions to it; unittest are available
+# in talk_unittest.py.
+#
+# Each 'component' that is built is defined in a .scons file.
+# See talk.Components(...) for further info on file naming convention.
+#
+# To add a new platform clone and modify the root_env object. Remember to add
+# the new environment object to the envs list otherwise it will not be included
+# in the build.
+#
+#
+#
+import talk
+import os
+import platform
+
+#-------------------------------------------------------------------------------
+# The build files/directories to 'build'.
+# If the name is the name of a directory then that directory shall contain a
+# .scons file with the same name as the directory itself:
+#  Ex: The directory session/phone contains a file called phone.scons
+#
+components = talk.Components("libjingle.scons")
+
+#-------------------------------------------------------------------------------
+# Build environments
+#
+
+# The list of build environments.
+envs = []
+
+# The root of all builds.
+root_env = Environment(
+  tools = [
+    'component_bits',
+    'component_setup',
+    'replace_strings',
+    'talk_noops',
+    #'talk_linux',
+  ],
+  BUILD_SCONSCRIPTS = components,
+  DESTINATION_ROOT = '$MAIN_DIR/build',
+  CPPPATH = [
+    '$OBJ_ROOT',     # generated headers are relative to here
+    '$MAIN_DIR/..',  # TODO: how can we use GOOGLECLIENT instead?
+  ],
+  CPPDEFINES = [
+    # Temp flag while porting to hammer.
+    'HAMMER_TIME=1',
+    'LOGGING=1',
+
+    # Feature selection
+    'FEATURE_ENABLE_SSL',
+    'FEATURE_ENABLE_VOICEMAIL',
+    'FEATURE_ENABLE_PSTN',
+    'HAVE_SRTP',
+  ]
+)
+
+# This is where we set common environments
+#
+# Detect if running on 64 bit or 32 bit host.
+DeclareBit('platform_arch_64bit', 'Host Platform is 64 Bit')
+if platform.architecture()[0] == "64bit":
+  root_env.SetBits('platform_arch_64bit')
+
+DeclareBit('use_static_openssl', 'Build OpenSSL as a static library')
+
+
+#-------------------------------------------------------------------------------
+# W I N D O W S
+#
+win_env = root_env.Clone(
+  tools = [
+    'atlmfc_vc80',
+    #'code_signing',
+    'component_targets_msvs',
+    'directx_9_0_c',
+    #'grid_builder',
+    'midl',
+    'target_platform_windows',
+  ],
+  # Don't use default vc80 midl.exe.  It doesn't understand vista_sdk idl files.
+  MIDL = '$PLATFORM_SDK_VISTA_6_0_DIR/Bin/midl.exe ',
+  WIX_DIR = '$GOOGLECLIENT/third_party/wix/v3_0_2925/files',
+  # Flags for debug and optimization are added to CCFLAGS instead
+  CCPDBFLAGS = '',
+  CCFLAGS_DEBUG = '',
+  CCFLAGS_OPTIMIZED = '',
+  # We force a x86 target even when building on x64 Windows platforms.
+  TARGET_ARCH = 'x86',
+)
+
+win_env.Append(
+  COMPONENT_LIBRARY_PUBLISH = True,  # Put dlls in output dir too
+  CCFLAGS = [
+    '/Fd${TARGET}.pdb', # pdb per object allows --jobs=
+    '/WX',          # warnings are errors
+    '/Zc:forScope', # handle 'for (int i = 0 ...)' right
+    '/EHs-c-',      # disable C++ EH
+    '/GR-',         # disable RTTI
+    '/wd4996',      # ignore POSIX deprecated warnings
+
+    # promote certain level 4 warnings
+    '/w14701',     # potentially uninitialized var
+    '/w14702',     # unreachable code
+    '/w14706',     # assignment within a conditional
+    '/w14709',     # comma operator within array index
+    '/w14063',     # case 'identifier' is not a valid value for switch of enum
+    '/w14064',     # switch of incomplete enum 'enumeration'
+    '/w14057',     # 'identifier1' indirection to slightly different base
+                   #   types from 'identifier2'
+    '/w14263',     # member function does not override any base class virtual
+                   #   member function
+    '/w14266',     # no override available for virtual memberfunction from base
+                   #  'type'; function is hidden
+    '/w14296',     # expression is always false
+    '/w14355',     # 'this' : used in base member initializer list
+  ],
+  CPPDEFINES = [
+    '_ATL_CSTRING_EXPLICIT_CONSTRUCTORS',
+    # TODO: encapsulate all string operations that are not based
+    # on std::string/std::wstring and make sure we use the safest versions
+    # available on all platforms.
+    '_CRT_SECURE_NO_WARNINGS',
+    '_SCL_SECURE_NO_WARNINGS',
+    '_USE_32BIT_TIME_T',
+    '_UNICODE',
+    'UNICODE',
+    '_HAS_EXCEPTIONS=0',
+    'WIN32',
+    # TODO: remove this from logging.cc and enable here instead.
+    #'WIN32_LEAN_AND_MEAN',
+
+    'WINVER=0x0500',
+    '_WIN32_WINNT=0x0501',
+    '_WIN32_IE=0x0501',
+    # The Vista platform SDK 6.0 needs at least
+    # this NTDDI version or else the headers
+    # that LMI includes from it won't compile.
+    'NTDDI_VERSION=NTDDI_WINXP',
+
+    # npapi.h requires the following:
+    '_WINDOWS',
+  ],
+  CPPPATH = [
+    '$THIRD_PARTY/wtl_71/include',
+    '$PLATFORM_SDK_VISTA_6_0_DIR/Include',
+  ],
+  LIBPATH = [
+    '$PLATFORM_SDK_VISTA_6_0_DIR/Lib'
+  ],
+  LINKFLAGS = [
+    '-manifest' # TODO: Why do we need this?
+  ],
+  MIDLFLAGS = [
+    '/win32',
+    '/I$PLATFORM_SDK_VISTA_6_0_DIR/include'
+  ]
+)
+
+# TODO: Figure out what this does; found it in
+# omaha/main.scons. This fixes the problem with redefinition
+# of OS_WINDOWS symbol.
+win_env.FilterOut(CPPDEFINES = ['OS_WINDOWS=OS_WINDOWS'])
+
+# Set up digital signing
+DeclareBit('test_signing', 'Sign binaries with the test certificate')
+win_env.SetBitFromOption('test_signing', False)
+if win_env.Bit('test_signing'):
+   win_env.Replace(
+     CERTIFICATE_PATH = win_env.File(
+         '$GOOGLECLIENT/tools/test_key/testkey.pfx').abspath,
+     CERTIFICATE_PASSWORD = 'test',
+   )
+AddTargetGroup('signed_binaries', 'digitally signed binaries can be built')
+
+win_dbg_env = win_env.Clone(
+  BUILD_TYPE = 'dbg',
+  BUILD_TYPE_DESCRIPTION = 'Windows debug build',
+  BUILD_GROUPS = ['default', 'all'],
+  tools = ['target_debug'],
+)
+
+win_dbg_env.Prepend(
+  CCFLAGS = [
+      '/ZI',     # enable debugging
+      '/Od',     # disable optimizations
+      '/MTd',    # link with LIBCMTD.LIB debug lib
+      '/RTC1',   # enable runtime checks
+  ],
+)
+
+envs.append(win_dbg_env)
+
+win_coverage_env = win_dbg_env.Clone(
+  tools = ['code_coverage'],
+  BUILD_TYPE = 'coverage',
+  BUILD_TYPE_DESCRIPTION = 'Windows code coverage build',
+  BUILD_GROUPS = ['all'],
+)
+
+win_coverage_env.Append(
+  CPPDEFINES = [
+    'COVERAGE_ENABLED',
+  ],
+)
+
+envs.append(win_coverage_env)
+
+win_opt_env = win_env.Clone(
+  BUILD_TYPE = 'opt',
+  BUILD_TYPE_DESCRIPTION = 'Windows opt build',
+  BUILD_GROUPS = ['all'],
+  tools = ['target_optimized'],
+)
+
+win_opt_env.Prepend(
+  CCFLAGS=[
+      '/Zi',     # enable debugging
+      '/O1',     # optimize for size
+      '/MT',     # link with LIBCMT.LIB (multi-threaded, static linked crt)
+      '/GS',     # enable security checks
+  ],
+  LINKFLAGS = [
+    '/safeseh',
+  ],
+)
+
+envs.append(win_opt_env)
+
+
+#-------------------------------------------------------------------------------
+# P O S I X
+#
+posix_env = root_env.Clone()
+posix_env.Append(
+  CPPDEFINES = [
+    'HASHNAMESPACE=__gnu_cxx',
+    'HASH_NAMESPACE=__gnu_cxx',
+    'POSIX',
+    'DISABLE_DYNAMIC_CAST',
+    'HAVE_OPENSSL_SSL_H=1',
+    # The POSIX standard says we have to define this.
+    '_REENTRANT',
+  ],
+  CCFLAGS = [
+    '-Wall',
+    '-Werror',
+    '-Wno-switch',
+    '-fno-exceptions',
+  ],
+  CXXFLAGS = [
+    '-Wno-non-virtual-dtor',
+    '-Wno-ctor-dtor-privacy',
+    '-fno-rtti',
+  ],
+)
+
+#-------------------------------------------------------------------------------
+# M A C OSX
+#
+mac_env = posix_env.Clone(
+  tools = [
+    'target_platform_mac',
+    #'talk_mac',
+    #'fill_plist',
+  ],
+)
+mac_env.Append(
+  CPPDEFINES = [
+    'OSX',
+    'MAC_OS_X_VERSION_MIN_REQUIRED=1040',
+  ],
+  CCFLAGS = [
+    '-m32',
+    '-arch', 'i386',
+    '-isysroot', '/Developer/SDKs/MacOSX10.5.sdk',
+    '-fasm-blocks',
+  ],
+  LINKFLAGS = [
+    '-Wl,-search_paths_first',
+    # This flag makes all members of a static library be included in the
+    # final exe - that increases the size of the exe, but without it
+    # Obj-C categories aren't properly included in the exe.
+    # TODO: consider only defining for libs that actually have objc.
+    '-ObjC',
+    '-arch', 'i386',
+    '-m32',
+  ],
+  FRAMEWORKS = [
+    'CoreServices',
+    'Carbon',
+    'Security',
+    'SystemConfiguration',
+    'OpenGL',
+    'CoreAudio',
+    'Quartz',
+    'QuickTime',
+    'Cocoa',
+    'QTKit',
+  ]
+)
+
+mac_dbg_env = mac_env.Clone(
+  BUILD_TYPE = 'dbg',
+  BUILD_TYPE_DESCRIPTION = 'Mac debug build',
+  BUILD_GROUPS = ['default', 'all'],
+  tools = ['target_debug'],
+)
+mac_dbg_env.Append(
+  CCFLAGS = [
+    '-O0',
+  ]
+)
+envs.append(mac_dbg_env)
+
+mac_opt_env = mac_env.Clone(
+  BUILD_TYPE = 'opt',
+  BUILD_TYPE_DESCRIPTION = 'Mac opt build',
+  BUILD_GROUPS = ['all'],
+  tools = ['target_optimized'],
+)
+mac_opt_env.Append(
+  CCFLAGS = [
+    # TODO: Figure out how mk can compile without
+    # this flag, then remove.  Confirmed asserts are preprocessed
+    # out.  Maybe it's a different version of gcc?
+    '-Wno-unused-variable',
+  ],
+)
+envs.append(mac_opt_env)
+
+#-------------------------------------------------------------------------------
+# L I N U X
+#
+linux_common_env = posix_env.Clone(
+  tools = [
+    'target_platform_linux',
+    #'talk_linux',
+  ],
+)
+
+linux_common_env.Append(
+  CPPDEFINES = [
+    'LINUX',
+    'HAVE_GLIB',
+#   'HAVE_DBUS_GLIB',
+  ],
+  CCFLAGS = [
+    # TODO: Some or all of this may be desirable for Mac too.
+    # Needed for link-time dead-code removal to work properly.
+    '-ffunction-sections',
+    '-fdata-sections',
+    # Needed for a clean ABI and for link-time dead-code removal to work
+    # properly.
+    '-fvisibility=hidden',
+    # Generate debugging info in the DWARF2 format.
+    '-gdwarf-2',
+    # Generate maximal debugging information. (It is stripped from what we ship
+    # to users, so we want it for both dbg and opt.)
+    '-g3',
+  ],
+  LINKFLAGS = [
+    # Enable dead-code removal.
+    '-Wl,--gc-sections',
+    '-Wl,--start-group',
+  ],
+  _LIBFLAGS = ['-Wl,--end-group'],
+)
+
+# Remove default rpath set by Hammer. Hammer sets it to LIB_DIR, which is wrong.
+# The rpath is the _run-time_ library search path for the resulting binary, i.e.
+# the one used by ld.so at load time. Setting it equal to the path to build
+# output on the build machine is nonsense.
+linux_common_env.Replace(
+  RPATH = [],
+)
+
+#-------------------------------------------------------------------------------
+# L I N U X -- T R A D I T I O N A L
+#
+# Settings that are specific to our desktop Linux targets.
+linux_env = linux_common_env.Clone()
+# OpenSSL has infamously poor ABI stability, so that building against one
+# version and running against a different one often will not work. Since our
+# non-ChromeOS Linux builds are used on many different distros and distro
+# versions, this means we can't safely dynamically link to OpenSSL because the
+# product would end up being broken on any computer with a different version
+# installed. So instead we build it ourself and statically link to it.
+linux_env.SetBits('use_static_openssl')
+linux_env.Append(
+  CCFLAGS = [
+    '-m32',
+  ],
+  LINKFLAGS = [
+    '-m32',
+  ],
+)
+
+linux_dbg_env = linux_env.Clone(
+  BUILD_TYPE = 'dbg',
+  BUILD_TYPE_DESCRIPTION = 'Linux debug build',
+  BUILD_GROUPS = ['default', 'all'],
+  tools = ['target_debug'],
+)
+# Remove -g set by hammer, which is not what we want (we have set -g3 above).
+linux_dbg_env.FilterOut(CCFLAGS = ['-g'])
+envs.append(linux_dbg_env)
+
+linux_opt_env = linux_env.Clone(
+  BUILD_TYPE = 'opt',
+  BUILD_TYPE_DESCRIPTION = 'Linux optimized build',
+  BUILD_GROUPS = ['all'],
+  tools = ['target_optimized'],
+)
+# Remove -O2 set by hammer, which is not what we want.
+linux_opt_env.FilterOut(CCFLAGS = ['-O2'])
+linux_opt_env.Append(CCFLAGS = ['-Os'])
+envs.append(linux_opt_env)
+
+
+
+# TODO(): Clone linux envs for 64bit.  See 'variant' documentation.
+
+# Create a group for installers
+AddTargetGroup('all_installers', 'installers that can be built')
+
+# Parse child .scons files
+BuildEnvironments(envs)
+
+# Explicitly set which targets to build when not stated on commandline
+Default(None)
+# Build the following, which excludes unit test output (ie running them)
+# To run unittests, specify the test to run, or run_all_tests.  See -h option.
+Default(['all_libraries', 'all_programs', 'all_test_programs'])
+
+# .sln creation code lifted from googleclient/bar/main.scons.  Must be after
+# the call to BuildEnvironments for all_foo aliases to be defined.
+# Run 'hammer --mode=all --vsproj' to generate
+DeclareBit('vsproj', 'Generate Visual Studio projects and solution files.')
+win_env.SetBitFromOption('vsproj', False)
+
+if win_env.Bit('vsproj'):
+  vs_env = win_env.Clone()
+  vs_env.Append(
+    COMPONENT_VS_SOURCE_SUFFIXES = [
+      '.def',
+      '.grd',
+      '.html',
+      '.idl',
+      '.mk',
+      '.txt',
+      '.py',
+      '.scons',
+      '.wxs.template',
+    ]
+  )
+
+  # Source project
+  p = vs_env.ComponentVSDirProject(
+    'flute_source',
+    ['$MAIN_DIR',
+     '$THIRD_PARTY'],
+    COMPONENT_VS_SOURCE_FOLDERS = [
+      # Files are assigned to first matching folder. Folder names of None
+      # are filters.
+      (None, '$DESTINATION_ROOT'),
+      ('flute', '$MAIN_DIR'),
+      ('google3', '$GOOGLE3'),
+      ('third_party', '$THIRD_PARTY'),
+    ],
+    # Force source project to main dir, so that Visual Studio can find the
+    # source files corresponding to build errors.
+    COMPONENT_VS_PROJECT_DIR = '$MAIN_DIR',
+  )
+  vs_env.AlwaysBuild(p)
+
+  # Solution and target projects
+  s = vs_env.ComponentVSSolution(
+    # 'libjingle',  # Please uncomment this line if you build VS proj files.
+    ['all_libraries', 'all_programs', 'all_test_programs'],
+    projects = [p],
+  )
+
+  print '***Unfortunately the vsproj creator isn\'t smart enough to '
+  print '***automatically get the correct output locations.  It is very easy'
+  print '***though to change it in the properties pane to the following'
+  print '***($SolutionDir)/build/<foo>/staging/<bar>.exe'
+  Default(None)
+  Default([s])
diff --git a/talk/p2p/base/candidate.h b/talk/p2p/base/candidate.h
new file mode 100644
index 0000000..e347ec5
--- /dev/null
+++ b/talk/p2p/base/candidate.h
@@ -0,0 +1,137 @@
+/*
+ * 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_P2P_BASE_CANDIDATE_H_
+#define TALK_P2P_BASE_CANDIDATE_H_
+
+#include <string>
+#include <sstream>
+#include "talk/base/socketaddress.h"
+
+namespace cricket {
+
+// Candidate for ICE based connection discovery.
+
+class Candidate {
+ public:
+  Candidate() : preference_(0), generation_(0) {}
+  Candidate(const std::string& name, const std::string& protocol,
+            const talk_base::SocketAddress& address, float preference,
+            const std::string& username, const std::string& password,
+            const std::string& type, const std::string& network_name,
+            uint32 generation)
+      : name_(name), protocol_(protocol), address_(address),
+        preference_(preference), username_(username), password_(password),
+        type_(type), network_name_(network_name), generation_(generation) {}
+
+  const std::string & name() const { return name_; }
+  void set_name(const std::string & name) { name_ = name; }
+
+  const std::string & protocol() const { return protocol_; }
+  void set_protocol(const std::string & protocol) { protocol_ = protocol; }
+
+  const talk_base::SocketAddress & address() const { return address_; }
+  void set_address(const talk_base::SocketAddress & address) {
+    address_ = address;
+  }
+
+  float preference() const { return preference_; }
+  void set_preference(const float preference) { preference_ = preference; }
+  const std::string preference_str() const {
+    std::ostringstream ost;
+    ost << preference_;
+    return ost.str();
+  }
+  void set_preference_str(const std::string & preference) {
+    std::istringstream ist(preference);
+    ist >> preference_;
+  }
+
+  const std::string & username() const { return username_; }
+  void set_username(const std::string & username) { username_ = username; }
+
+  const std::string & password() const { return password_; }
+  void set_password(const std::string & password) { password_ = password; }
+
+  const std::string & type() const { return type_; }
+  void set_type(const std::string & type) { type_ = type; }
+
+  const std::string & network_name() const { return network_name_; }
+  void set_network_name(const std::string & network_name) {
+    network_name_ = network_name;
+  }
+
+  // Candidates in a new generation replace those in the old generation.
+  uint32 generation() const { return generation_; }
+  void set_generation(uint32 generation) { generation_ = generation; }
+  const std::string generation_str() const {
+    std::ostringstream ost;
+    ost << generation_;
+    return ost.str();
+  }
+  void set_generation_str(const std::string& str) {
+    std::istringstream ist(str);
+    ist >> generation_;
+  }
+
+  // Determines whether this candidate is equivalent to the given one.
+  bool IsEquivalent(const Candidate& c) const {
+    // We ignore the network name, since that is just debug information, and
+    // the preference, since that should be the same if the rest is (and it's
+    // a float so equality checking is always worrisome).
+    return (name_ == c.name_) &&
+           (protocol_ == c.protocol_) &&
+           (address_ == c.address_) &&
+           (username_ == c.username_) &&
+           (password_ == c.password_) &&
+           (type_ == c.type_) &&
+           (generation_ == c.generation_);
+  }
+
+  std::string ToString() const {
+    std::ostringstream ost;
+    ost << "Cand[" << name_ << ":" << type_ << ":" << protocol_ << ":"
+        << network_name_ << ":" << address_.ToString() << ":"
+        << username_ << ":" << password_ << "]";
+    return ost.str();
+  }
+
+ private:
+  std::string name_;
+  std::string protocol_;
+  talk_base::SocketAddress address_;
+  float preference_;
+  std::string username_;
+  std::string password_;
+  std::string type_;
+  std::string network_name_;
+  uint32 generation_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_CANDIDATE_H_
diff --git a/talk/p2p/base/common.h b/talk/p2p/base/common.h
new file mode 100644
index 0000000..5a38180
--- /dev/null
+++ b/talk/p2p/base/common.h
@@ -0,0 +1,37 @@
+/*
+ * 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_P2P_BASE_COMMON_H_
+#define TALK_P2P_BASE_COMMON_H_
+
+#include "talk/base/logging.h"
+
+// Common log description format for jingle messages
+#define LOG_J(sev, obj) LOG(sev) << "Jingle:" << obj->ToString() << ": "
+#define LOG_JV(sev, obj) LOG_V(sev) << "Jingle:" << obj->ToString() << ": "
+
+#endif  // TALK_P2P_BASE_COMMON_H_
diff --git a/talk/p2p/base/constants.cc b/talk/p2p/base/constants.cc
new file mode 100644
index 0000000..f44ce21
--- /dev/null
+++ b/talk/p2p/base/constants.cc
@@ -0,0 +1,229 @@
+/*
+ * 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 <string>
+
+#include "talk/p2p/base/constants.h"
+#include "talk/xmllite/qname.h"
+
+namespace cricket {
+
+const std::string NS_EMPTY("");
+const std::string NS_JINGLE("urn:xmpp:jingle:1");
+const std::string NS_GINGLE("http://www.google.com/session");
+
+// actions (aka <session> or <jingle>)
+const buzz::QName QN_ACTION(true, NS_EMPTY, "action");
+const std::string LN_INITIATOR("initiator");
+const buzz::QName QN_INITIATOR(true, NS_EMPTY, LN_INITIATOR);
+const buzz::QName QN_CREATOR(true, NS_EMPTY, "creator");
+
+const buzz::QName QN_JINGLE(true, NS_JINGLE, "jingle");
+const buzz::QName QN_JINGLE_CONTENT(true, NS_JINGLE, "content");
+const buzz::QName QN_JINGLE_CONTENT_NAME(true, NS_EMPTY, "name");
+const buzz::QName QN_JINGLE_CONTENT_MEDIA(true, NS_EMPTY, "media");
+const buzz::QName QN_JINGLE_REASON(true, NS_JINGLE, "reason");
+const std::string JINGLE_CONTENT_MEDIA_AUDIO("audio");
+const std::string JINGLE_CONTENT_MEDIA_VIDEO("video");
+const std::string JINGLE_ACTION_SESSION_INITIATE("session-initiate");
+const std::string JINGLE_ACTION_SESSION_INFO("session-info");
+const std::string JINGLE_ACTION_SESSION_ACCEPT("session-accept");
+const std::string JINGLE_ACTION_SESSION_TERMINATE("session-terminate");
+const std::string JINGLE_ACTION_TRANSPORT_INFO("transport-info");
+const std::string JINGLE_ACTION_TRANSPORT_ACCEPT("transport-accept");
+
+const buzz::QName QN_GINGLE_SESSION(true, NS_GINGLE, "session");
+const std::string GINGLE_ACTION_INITIATE("initiate");
+const std::string GINGLE_ACTION_INFO("info");
+const std::string GINGLE_ACTION_ACCEPT("accept");
+const std::string GINGLE_ACTION_REJECT("reject");
+const std::string GINGLE_ACTION_TERMINATE("terminate");
+const std::string GINGLE_ACTION_CANDIDATES("candidates");
+const std::string GINGLE_ACTION_NOTIFY("notify");
+const std::string GINGLE_ACTION_UPDATE("update");
+const std::string GINGLE_ACTION_VIEW("view");
+
+const std::string LN_ERROR("error");
+const buzz::QName QN_GINGLE_REDIRECT(true, NS_GINGLE, "redirect");
+const std::string STR_REDIRECT_PREFIX("xmpp:");
+
+// Session Contents (aka Gingle <session><description>
+//                   or Jingle <content><description>)
+const std::string LN_DESCRIPTION("description");
+const std::string LN_PAYLOADTYPE("payload-type");
+const buzz::QName QN_ID(true, NS_EMPTY, "id");
+const buzz::QName QN_SID(true, NS_EMPTY, "sid");
+const buzz::QName QN_NAME(true, NS_EMPTY, "name");
+const buzz::QName QN_CLOCKRATE(true, NS_EMPTY, "clockrate");
+const buzz::QName QN_BITRATE(true, NS_EMPTY, "bitrate");
+const buzz::QName QN_CHANNELS(true, NS_EMPTY, "channels");
+const buzz::QName QN_WIDTH(true, NS_EMPTY, "width");
+const buzz::QName QN_HEIGHT(true, NS_EMPTY, "height");
+const buzz::QName QN_FRAMERATE(true, NS_EMPTY, "framerate");
+const std::string LN_NAME("name");
+const std::string LN_VALUE("value");
+const buzz::QName QN_PAYLOADTYPE_PARAMETER_NAME(true, NS_EMPTY, LN_NAME);
+const buzz::QName QN_PAYLOADTYPE_PARAMETER_VALUE(true, NS_EMPTY, LN_VALUE);
+const std::string PAYLOADTYPE_PARAMETER_BITRATE("bitrate");
+const std::string PAYLOADTYPE_PARAMETER_HEIGHT("height");
+const std::string PAYLOADTYPE_PARAMETER_WIDTH("width");
+const std::string PAYLOADTYPE_PARAMETER_FRAMERATE("framerate");
+const std::string LN_BANDWIDTH("bandwidth");
+
+const std::string CN_AUDIO("audio");
+const std::string CN_VIDEO("video");
+const std::string CN_OTHER("main");
+
+const std::string NS_JINGLE_RTP("urn:xmpp:jingle:apps:rtp:1");
+const buzz::QName QN_JINGLE_RTP_CONTENT(
+    true, NS_JINGLE_RTP, LN_DESCRIPTION);
+const buzz::QName QN_JINGLE_RTP_PAYLOADTYPE(
+    true, NS_JINGLE_RTP, LN_PAYLOADTYPE);
+const buzz::QName QN_JINGLE_RTP_BANDWIDTH(
+    true, NS_JINGLE_RTP, LN_BANDWIDTH);
+const buzz::QName QN_PARAMETER(true, NS_JINGLE_RTP, "parameter");
+
+const std::string NS_GINGLE_AUDIO("http://www.google.com/session/phone");
+const buzz::QName QN_GINGLE_AUDIO_CONTENT(
+    true, NS_GINGLE_AUDIO, LN_DESCRIPTION);
+const buzz::QName QN_GINGLE_AUDIO_PAYLOADTYPE(
+    true, NS_GINGLE_AUDIO, LN_PAYLOADTYPE);
+const buzz::QName QN_GINGLE_AUDIO_SRCID(true, NS_GINGLE_AUDIO, "src-id");
+const std::string NS_GINGLE_VIDEO("http://www.google.com/session/video");
+const buzz::QName QN_GINGLE_VIDEO_CONTENT(
+    true, NS_GINGLE_VIDEO, LN_DESCRIPTION);
+const buzz::QName QN_GINGLE_VIDEO_PAYLOADTYPE(
+    true, NS_GINGLE_VIDEO, LN_PAYLOADTYPE);
+const buzz::QName QN_GINGLE_VIDEO_SRCID(true, NS_GINGLE_VIDEO, "src-id");
+const buzz::QName QN_GINGLE_VIDEO_BANDWIDTH(
+    true, NS_GINGLE_VIDEO, LN_BANDWIDTH);
+
+// Crypto support.
+const buzz::QName QN_ENCRYPTION(true, NS_JINGLE_RTP, "encryption");
+const buzz::QName QN_ENCRYPTION_REQUIRED(true, NS_EMPTY, "required");
+const buzz::QName QN_CRYPTO(true, NS_JINGLE_RTP, "crypto");
+const buzz::QName QN_GINGLE_AUDIO_CRYPTO_USAGE(true, NS_GINGLE_AUDIO, "usage");
+const buzz::QName QN_GINGLE_VIDEO_CRYPTO_USAGE(true, NS_GINGLE_VIDEO, "usage");
+const buzz::QName QN_CRYPTO_SUITE(true, NS_EMPTY, "crypto-suite");
+const buzz::QName QN_CRYPTO_KEY_PARAMS(true, NS_EMPTY, "key-params");
+const buzz::QName QN_CRYPTO_TAG(true, NS_EMPTY, "tag");
+const buzz::QName QN_CRYPTO_SESSION_PARAMS(true, NS_EMPTY, "session-params");
+
+// transports and candidates
+const std::string LN_TRANSPORT("transport");
+const std::string LN_CANDIDATE("candidate");
+const buzz::QName QN_UFRAG(true, cricket::NS_EMPTY, "ufrag");
+const buzz::QName QN_PWD(true, cricket::NS_EMPTY, "pwd");
+const buzz::QName QN_COMPONENT(true, cricket::NS_EMPTY, "component");
+const buzz::QName QN_IP(true, cricket::NS_EMPTY, "ip");
+const buzz::QName QN_PORT(true, cricket::NS_EMPTY, "port");
+const buzz::QName QN_NETWORK(true, cricket::NS_EMPTY, "network");
+const buzz::QName QN_GENERATION(true, cricket::NS_EMPTY, "generation");
+const buzz::QName QN_PRIORITY(true, cricket::NS_EMPTY, "priority");
+const buzz::QName QN_PROTOCOL(true, cricket::NS_EMPTY, "protocol");
+const std::string JINGLE_CANDIDATE_TYPE_PEER_STUN("prflx");
+const std::string JINGLE_CANDIDATE_TYPE_SERVER_STUN("srflx");
+const std::string JINGLE_CANDIDATE_NAME_RTP("1");
+const std::string JINGLE_CANDIDATE_NAME_RTCP("2");
+
+// TODO Once we are full ICE-UDP compliant, use this namespace.
+// For now, just use the same as NS_GINGLE_P2P.
+// const std::string NS_JINGLE_ICE_UDP("urn:xmpp:jingle:transports:ice-udp:1");
+const std::string NS_GINGLE_P2P("http://www.google.com/transport/p2p");
+const buzz::QName QN_GINGLE_P2P_TRANSPORT(true, NS_GINGLE_P2P, LN_TRANSPORT);
+const buzz::QName QN_GINGLE_P2P_CANDIDATE(true, NS_GINGLE_P2P, LN_CANDIDATE);
+const buzz::QName QN_GINGLE_P2P_UNKNOWN_CHANNEL_NAME(
+    true, NS_GINGLE_P2P, "unknown-channel-name");
+const buzz::QName QN_GINGLE_CANDIDATE(true, NS_GINGLE, LN_CANDIDATE);
+const buzz::QName QN_ADDRESS(true, cricket::NS_EMPTY, "address");
+const buzz::QName QN_USERNAME(true, cricket::NS_EMPTY, "username");
+const buzz::QName QN_PASSWORD(true, cricket::NS_EMPTY, "password");
+const buzz::QName QN_PREFERENCE(true, cricket::NS_EMPTY, "preference");
+const std::string GINGLE_CANDIDATE_TYPE_STUN("stun");
+const std::string GINGLE_CANDIDATE_NAME_RTP("rtp");
+const std::string GINGLE_CANDIDATE_NAME_RTCP("rtcp");
+const std::string GINGLE_CANDIDATE_NAME_VIDEO_RTP("video_rtp");
+const std::string GINGLE_CANDIDATE_NAME_VIDEO_RTCP("video_rtcp");
+
+// terminate reasons and errors
+const std::string JINGLE_ERROR_BAD_REQUEST("bad-request");
+const std::string JINGLE_ERROR_OUT_OF_ORDER("out-of-order");
+const std::string JINGLE_ERROR_UNKNOWN_SESSION("unknown-session");
+
+// Call terminate reasons from XEP-166
+const std::string STR_TERMINATE_DECLINE("decline");
+const std::string STR_TERMINATE_SUCCESS("success");
+const std::string STR_TERMINATE_ERROR("general-error");
+const std::string STR_TERMINATE_INCOMPATIBLE_PARAMETERS(
+    "incompatible-parameters");
+
+// Old terminate reasons used by cricket
+const std::string STR_TERMINATE_CALL_ENDED("call-ended");
+const std::string STR_TERMINATE_RECIPIENT_UNAVAILABLE("recipient-unavailable");
+const std::string STR_TERMINATE_RECIPIENT_BUSY("recipient-busy");
+const std::string STR_TERMINATE_INSUFFICIENT_FUNDS("insufficient-funds");
+const std::string STR_TERMINATE_NUMBER_MALFORMED("number-malformed");
+const std::string STR_TERMINATE_NUMBER_DISALLOWED("number-disallowed");
+const std::string STR_TERMINATE_PROTOCOL_ERROR("protocol-error");
+const std::string STR_TERMINATE_INTERNAL_SERVER_ERROR("internal-server-error");
+const std::string STR_TERMINATE_UNKNOWN_ERROR("unknown-error");
+
+// Session notify messages
+const buzz::QName QN_GINGLE_NOTIFY(true, NS_GINGLE, "notify");
+const buzz::QName QN_GINGLE_NOTIFY_NICK(true, cricket::NS_EMPTY, "nick");
+const buzz::QName QN_GINGLE_NOTIFY_SOURCE(true, NS_GINGLE, "source");
+const buzz::QName QN_GINGLE_NOTIFY_SOURCE_MTYPE(
+    true, cricket::NS_EMPTY, "mtype");
+const buzz::QName QN_GINGLE_NOTIFY_SOURCE_SSRC(true, cricket::NS_EMPTY, "ssrc");
+const std::string GINGLE_NOTIFY_SOURCE_MTYPE_AUDIO("audio");
+const std::string GINGLE_NOTIFY_SOURCE_MTYPE_VIDEO("video");
+
+// Session view messages
+const buzz::QName QN_GINGLE_VIEW(true, cricket::NS_EMPTY, "view");
+const buzz::QName QN_GINGLE_VIEW_TYPE(true, cricket::NS_EMPTY, "type");
+const buzz::QName QN_GINGLE_VIEW_NICK(true, cricket::NS_EMPTY, "nick");
+const buzz::QName QN_GINGLE_VIEW_MEDIA_TYPE(true, cricket::NS_EMPTY, "mtype");
+const buzz::QName QN_GINGLE_VIEW_SSRC(true, cricket::NS_EMPTY, "ssrc");
+const std::string GINGLE_VIEW_TYPE_STATIC("static");
+const std::string GINGLE_VIEW_TYPE_DYNAMIC("dynamic");
+const std::string GINGLE_VIEW_MEDIA_TYPE_AUDIO("audio");
+const std::string GINGLE_VIEW_MEDIA_TYPE_VIDEO("video");
+const buzz::QName QN_GINGLE_VIEW_PARAMS(true, cricket::NS_EMPTY, "params");
+const buzz::QName QN_GINGLE_VIEW_PARAMS_WIDTH(true, cricket::NS_EMPTY, "width");
+const buzz::QName QN_GINGLE_VIEW_PARAMS_HEIGHT(
+    true, cricket::NS_EMPTY, "height");
+const buzz::QName QN_GINGLE_VIEW_PARAMS_FRAMERATE(
+    true, cricket::NS_EMPTY, "framerate");
+
+
+// old stuff
+#ifdef FEATURE_ENABLE_VOICEMAIL
+const std::string NS_VOICEMAIL("http://www.google.com/session/voicemail");
+const buzz::QName QN_VOICEMAIL_REGARDING(true, NS_VOICEMAIL, "regarding");
+#endif
+
+}  // namespace cricket
diff --git a/talk/p2p/base/constants.h b/talk/p2p/base/constants.h
new file mode 100644
index 0000000..cd2e2c5
--- /dev/null
+++ b/talk/p2p/base/constants.h
@@ -0,0 +1,242 @@
+/*
+ * 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_P2P_BASE_CONSTANTS_H_
+#define TALK_P2P_BASE_CONSTANTS_H_
+
+#include <string>
+#include "talk/xmllite/qname.h"
+
+// This file contains constants related to signaling that are used in various
+// classes in this directory.
+
+namespace cricket {
+
+// NS_ == namespace
+// QN_ == buzz::QName (namespace + name)
+// LN_ == "local name" == QName::LocalPart()
+//   these are useful when you need to find a tag
+//   that has different namespaces (like <description> or <transport>)
+
+extern const std::string NS_EMPTY;
+extern const std::string NS_JINGLE;
+extern const std::string NS_GINGLE;
+
+enum SignalingProtocol {
+  PROTOCOL_JINGLE,
+  PROTOCOL_GINGLE,
+  PROTOCOL_HYBRID,
+};
+
+// actions (aka Gingle <session> or Jingle <jingle>)
+extern const buzz::QName QN_ACTION;
+extern const std::string LN_INITIATOR;
+extern const buzz::QName QN_INITIATOR;
+extern const buzz::QName QN_CREATOR;
+
+extern const buzz::QName QN_JINGLE;
+extern const buzz::QName QN_JINGLE_CONTENT;
+extern const buzz::QName QN_JINGLE_CONTENT_NAME;
+extern const buzz::QName QN_JINGLE_CONTENT_MEDIA;
+extern const buzz::QName QN_JINGLE_REASON;
+extern const std::string JINGLE_CONTENT_MEDIA_AUDIO;
+extern const std::string JINGLE_CONTENT_MEDIA_VIDEO;
+extern const std::string JINGLE_ACTION_SESSION_INITIATE;
+extern const std::string JINGLE_ACTION_SESSION_INFO;
+extern const std::string JINGLE_ACTION_SESSION_ACCEPT;
+extern const std::string JINGLE_ACTION_SESSION_TERMINATE;
+extern const std::string JINGLE_ACTION_TRANSPORT_INFO;
+extern const std::string JINGLE_ACTION_TRANSPORT_ACCEPT;
+
+extern const buzz::QName QN_GINGLE_SESSION;
+extern const std::string GINGLE_ACTION_INITIATE;
+extern const std::string GINGLE_ACTION_INFO;
+extern const std::string GINGLE_ACTION_ACCEPT;
+extern const std::string GINGLE_ACTION_REJECT;
+extern const std::string GINGLE_ACTION_TERMINATE;
+extern const std::string GINGLE_ACTION_CANDIDATES;
+extern const std::string GINGLE_ACTION_NOTIFY;
+extern const std::string GINGLE_ACTION_UPDATE;
+extern const std::string GINGLE_ACTION_VIEW;
+
+extern const std::string LN_ERROR;
+extern const buzz::QName QN_GINGLE_REDIRECT;
+extern const std::string STR_REDIRECT_PREFIX;
+
+// Session Contents (aka Gingle <session><description>
+//                   or Jingle <content><description>)
+extern const std::string LN_DESCRIPTION;
+extern const std::string LN_PAYLOADTYPE;
+extern const buzz::QName QN_ID;
+extern const buzz::QName QN_SID;
+extern const buzz::QName QN_NAME;
+extern const buzz::QName QN_CLOCKRATE;
+extern const buzz::QName QN_BITRATE;
+extern const buzz::QName QN_CHANNELS;
+extern const buzz::QName QN_WIDTH;
+extern const buzz::QName QN_HEIGHT;
+extern const buzz::QName QN_FRAMERATE;
+extern const buzz::QName QN_PARAMETER;
+extern const std::string LN_NAME;
+extern const std::string LN_VALUE;
+extern const buzz::QName QN_PAYLOADTYPE_PARAMETER_NAME;
+extern const buzz::QName QN_PAYLOADTYPE_PARAMETER_VALUE;
+extern const std::string PAYLOADTYPE_PARAMETER_BITRATE;
+extern const std::string PAYLOADTYPE_PARAMETER_HEIGHT;
+extern const std::string PAYLOADTYPE_PARAMETER_WIDTH;
+extern const std::string PAYLOADTYPE_PARAMETER_FRAMERATE;
+extern const std::string LN_BANDWIDTH;
+
+// CN_ == "content name".  When we initiate a session, we choose the
+// name, and when we receive a Gingle session, we provide default
+// names (since Gingle has no content names).  But when we receive a
+// Jingle call, the content name can be anything, so don't rely on
+// these values being the same as the ones received.
+extern const std::string CN_AUDIO;
+extern const std::string CN_VIDEO;
+extern const std::string CN_OTHER;
+
+extern const std::string NS_JINGLE_RTP;
+extern const buzz::QName QN_JINGLE_RTP_CONTENT;
+extern const buzz::QName QN_JINGLE_RTP_PAYLOADTYPE;
+extern const buzz::QName QN_JINGLE_RTP_BANDWIDTH;
+
+extern const std::string NS_GINGLE_AUDIO;
+extern const buzz::QName QN_GINGLE_AUDIO_CONTENT;
+extern const buzz::QName QN_GINGLE_AUDIO_PAYLOADTYPE;
+extern const buzz::QName QN_GINGLE_AUDIO_SRCID;
+extern const std::string NS_GINGLE_VIDEO;
+extern const buzz::QName QN_GINGLE_VIDEO_CONTENT;
+extern const buzz::QName QN_GINGLE_VIDEO_PAYLOADTYPE;
+extern const buzz::QName QN_GINGLE_VIDEO_SRCID;
+extern const buzz::QName QN_GINGLE_VIDEO_BANDWIDTH;
+
+// Crypto support.
+extern const buzz::QName QN_ENCRYPTION;
+extern const buzz::QName QN_ENCRYPTION_REQUIRED;
+extern const buzz::QName QN_CRYPTO;
+extern const buzz::QName QN_GINGLE_AUDIO_CRYPTO_USAGE;
+extern const buzz::QName QN_GINGLE_VIDEO_CRYPTO_USAGE;
+extern const buzz::QName QN_CRYPTO_SUITE;
+extern const buzz::QName QN_CRYPTO_KEY_PARAMS;
+extern const buzz::QName QN_CRYPTO_TAG;
+extern const buzz::QName QN_CRYPTO_SESSION_PARAMS;
+
+// transports and candidates
+extern const std::string LN_TRANSPORT;
+extern const std::string LN_CANDIDATE;
+extern const buzz::QName QN_JINGLE_P2P_TRANSPORT;
+extern const buzz::QName QN_JINGLE_P2P_CANDIDATE;
+extern const buzz::QName QN_UFRAG;
+extern const buzz::QName QN_COMPONENT;
+extern const buzz::QName QN_PWD;
+extern const buzz::QName QN_IP;
+extern const buzz::QName QN_PORT;
+extern const buzz::QName QN_NETWORK;
+extern const buzz::QName QN_GENERATION;
+extern const buzz::QName QN_PRIORITY;
+extern const buzz::QName QN_PROTOCOL;
+extern const std::string JINGLE_CANDIDATE_TYPE_PEER_STUN;
+extern const std::string JINGLE_CANDIDATE_TYPE_SERVER_STUN;
+extern const std::string JINGLE_CANDIDATE_NAME_RTP;
+extern const std::string JINGLE_CANDIDATE_NAME_RTCP;
+
+extern const std::string NS_GINGLE_P2P;
+extern const buzz::QName QN_GINGLE_P2P_TRANSPORT;
+extern const buzz::QName QN_GINGLE_P2P_CANDIDATE;
+extern const buzz::QName QN_GINGLE_P2P_UNKNOWN_CHANNEL_NAME;
+extern const buzz::QName QN_GINGLE_CANDIDATE;
+extern const buzz::QName QN_ADDRESS;
+extern const buzz::QName QN_USERNAME;
+extern const buzz::QName QN_PASSWORD;
+extern const buzz::QName QN_PREFERENCE;
+extern const std::string GINGLE_CANDIDATE_TYPE_STUN;
+extern const std::string GINGLE_CANDIDATE_NAME_RTP;
+extern const std::string GINGLE_CANDIDATE_NAME_RTCP;
+extern const std::string GINGLE_CANDIDATE_NAME_VIDEO_RTP;
+extern const std::string GINGLE_CANDIDATE_NAME_VIDEO_RTCP;
+
+extern const std::string NS_GINGLE_RAW;
+extern const buzz::QName QN_GINGLE_RAW_TRANSPORT;
+extern const buzz::QName QN_GINGLE_RAW_CHANNEL;
+
+// terminate reasons and errors: see http://xmpp.org/extensions/xep-0166.html
+extern const std::string JINGLE_ERROR_BAD_REQUEST;  // like parse error
+// got transport-info before session-initiate, for example
+extern const std::string JINGLE_ERROR_OUT_OF_ORDER;
+extern const std::string JINGLE_ERROR_UNKNOWN_SESSION;
+
+// Call terminate reasons from XEP-166
+extern const std::string STR_TERMINATE_DECLINE;  // polite reject
+extern const std::string STR_TERMINATE_SUCCESS;  // polite hangup
+extern const std::string STR_TERMINATE_ERROR;  // something bad happened
+extern const std::string STR_TERMINATE_INCOMPATIBLE_PARAMETERS;  // no codecs?
+
+// Old terminate reasons used by cricket
+extern const std::string STR_TERMINATE_CALL_ENDED;
+extern const std::string STR_TERMINATE_RECIPIENT_UNAVAILABLE;
+extern const std::string STR_TERMINATE_RECIPIENT_BUSY;
+extern const std::string STR_TERMINATE_INSUFFICIENT_FUNDS;
+extern const std::string STR_TERMINATE_NUMBER_MALFORMED;
+extern const std::string STR_TERMINATE_NUMBER_DISALLOWED;
+extern const std::string STR_TERMINATE_PROTOCOL_ERROR;
+extern const std::string STR_TERMINATE_INTERNAL_SERVER_ERROR;
+extern const std::string STR_TERMINATE_UNKNOWN_ERROR;
+
+// Session notify messages
+extern const buzz::QName QN_GINGLE_NOTIFY;
+extern const buzz::QName QN_GINGLE_NOTIFY_NICK;
+extern const buzz::QName QN_GINGLE_NOTIFY_SOURCE;
+extern const buzz::QName QN_GINGLE_NOTIFY_SOURCE_MTYPE;
+extern const buzz::QName QN_GINGLE_NOTIFY_SOURCE_SSRC;
+extern const std::string GINGLE_NOTIFY_SOURCE_MTYPE_AUDIO;
+extern const std::string GINGLE_NOTIFY_SOURCE_MTYPE_VIDEO;
+
+// Session view messages
+extern const buzz::QName QN_GINGLE_VIEW;
+extern const buzz::QName QN_GINGLE_VIEW_TYPE;
+extern const buzz::QName QN_GINGLE_VIEW_NICK;
+extern const buzz::QName QN_GINGLE_VIEW_MEDIA_TYPE;
+extern const buzz::QName QN_GINGLE_VIEW_SSRC;
+extern const std::string GINGLE_VIEW_TYPE_STATIC;
+extern const std::string GINGLE_VIEW_TYPE_DYNAMIC;
+extern const std::string GINGLE_VIEW_MEDIA_TYPE_AUDIO;
+extern const std::string GINGLE_VIEW_MEDIA_TYPE_VIDEO;
+extern const buzz::QName QN_GINGLE_VIEW_PARAMS;
+extern const buzz::QName QN_GINGLE_VIEW_PARAMS_WIDTH;
+extern const buzz::QName QN_GINGLE_VIEW_PARAMS_HEIGHT;
+extern const buzz::QName QN_GINGLE_VIEW_PARAMS_FRAMERATE;
+
+// old stuff
+#ifdef FEATURE_ENABLE_VOICEMAIL
+extern const std::string NS_VOICEMAIL;
+extern const buzz::QName QN_VOICEMAIL_REGARDING;
+#endif
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_CONSTANTS_H_
diff --git a/talk/p2p/base/p2ptransport.cc b/talk/p2p/base/p2ptransport.cc
new file mode 100644
index 0000000..dd170ff
--- /dev/null
+++ b/talk/p2p/base/p2ptransport.cc
@@ -0,0 +1,201 @@
+/*
+ * 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/p2p/base/p2ptransport.h"
+
+#include <string>
+#include <vector>
+
+#include "talk/base/base64.h"
+#include "talk/base/common.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/p2ptransportchannel.h"
+#include "talk/p2p/base/parsing.h"
+#include "talk/p2p/base/sessionmanager.h"
+#include "talk/p2p/base/sessionmessages.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/constants.h"
+
+namespace {
+
+// We only allow usernames to be this many characters or fewer.
+const size_t kMaxUsernameSize = 16;
+
+}  // namespace
+
+namespace cricket {
+
+P2PTransport::P2PTransport(talk_base::Thread* signaling_thread,
+                           talk_base::Thread* worker_thread,
+                           PortAllocator* allocator)
+    : Transport(signaling_thread, worker_thread,
+                NS_GINGLE_P2P, allocator) {
+}
+
+P2PTransport::~P2PTransport() {
+  DestroyAllChannels();
+}
+
+void P2PTransport::OnTransportError(const buzz::XmlElement* error) {
+  // Need to know if it was <unknown-channel name="xxx">.
+  ASSERT(error->Name().Namespace() == type());
+  if ((error->Name() == QN_GINGLE_P2P_UNKNOWN_CHANNEL_NAME)
+      && error->HasAttr(buzz::QN_NAME)) {
+    std::string channel_name = error->Attr(buzz::QN_NAME);
+    if (HasChannel(channel_name)) {
+      SignalChannelGone(this, channel_name);
+    }
+  }
+}
+
+
+bool P2PTransportParser::ParseCandidates(SignalingProtocol protocol,
+                                         const buzz::XmlElement* elem,
+                                         Candidates* candidates,
+                                         ParseError* error) {
+  // TODO: Once we implement standard ICE-UDP, parse the
+  // candidates according to XEP-176.
+  for (const buzz::XmlElement* candidate_elem = elem->FirstElement();
+       candidate_elem != NULL;
+       candidate_elem = candidate_elem->NextElement()) {
+    // Only look at local part because it might be <session><candidate>
+    //                                          or <tranport><candidate>.
+    if (candidate_elem->Name().LocalPart() == LN_CANDIDATE) {
+      Candidate candidate;
+      if (!ParseCandidate(candidate_elem, &candidate, error))
+        return false;
+      candidates->push_back(candidate);
+    }
+  }
+  return true;
+}
+
+bool P2PTransportParser::ParseCandidate(const buzz::XmlElement* elem,
+                                        Candidate* candidate,
+                                        ParseError* error) {
+  if (!elem->HasAttr(buzz::QN_NAME) ||
+      !elem->HasAttr(QN_ADDRESS) ||
+      !elem->HasAttr(QN_PORT) ||
+      !elem->HasAttr(QN_USERNAME) ||
+      !elem->HasAttr(QN_PREFERENCE) ||
+      !elem->HasAttr(QN_PROTOCOL) ||
+      !elem->HasAttr(QN_GENERATION)) {
+    return BadParse("candidate missing required attribute", error);
+  }
+
+  talk_base::SocketAddress address;
+  if (!ParseAddress(elem, QN_ADDRESS, QN_PORT, &address, error))
+    return false;
+
+  candidate->set_name(elem->Attr(buzz::QN_NAME));
+  candidate->set_address(address);
+  candidate->set_username(elem->Attr(QN_USERNAME));
+  candidate->set_preference_str(elem->Attr(QN_PREFERENCE));
+  candidate->set_protocol(elem->Attr(QN_PROTOCOL));
+  candidate->set_generation_str(elem->Attr(QN_GENERATION));
+  if (elem->HasAttr(QN_PASSWORD))
+    candidate->set_password(elem->Attr(QN_PASSWORD));
+  if (elem->HasAttr(buzz::QN_TYPE))
+    candidate->set_type(elem->Attr(buzz::QN_TYPE));
+  if (elem->HasAttr(QN_NETWORK))
+    candidate->set_network_name(elem->Attr(QN_NETWORK));
+
+  if (!VerifyUsernameFormat(candidate->username(), error))
+    return false;
+
+  return true;
+}
+
+bool P2PTransportParser::VerifyUsernameFormat(const std::string& username,
+                                              ParseError* error) {
+  if (username.size() > kMaxUsernameSize)
+    return BadParse("candidate username is too long", error);
+  if (!talk_base::Base64::IsBase64Encoded(username))
+    return BadParse(
+        "candidate username has non-base64 encoded characters", error);
+  return true;
+}
+
+const buzz::QName& GetCandidateQName(SignalingProtocol protocol) {
+  if (protocol == PROTOCOL_GINGLE) {
+    return QN_GINGLE_CANDIDATE;
+  } else {
+    // TODO: Once we implement standard ICE-UDP, use the
+    // XEP-176 namespace.
+    return QN_GINGLE_P2P_CANDIDATE;
+  }
+}
+
+bool P2PTransportParser::WriteCandidates(SignalingProtocol protocol,
+                                         const Candidates& candidates,
+                                         XmlElements* candidate_elems,
+                                         WriteError* error) {
+  // TODO: Once we implement standard ICE-UDP, parse the
+  // candidates according to XEP-176.
+  for (std::vector<Candidate>::const_iterator iter = candidates.begin();
+       iter != candidates.end(); ++iter) {
+    buzz::XmlElement* cand_elem =
+        new buzz::XmlElement(GetCandidateQName(protocol));
+    if (!WriteCandidate(*iter, cand_elem, error))
+      return false;
+    candidate_elems->push_back(cand_elem);
+  }
+  return true;
+}
+
+bool P2PTransportParser::WriteCandidate(const Candidate& candidate,
+                                        buzz::XmlElement* elem,
+                                        WriteError* error) {
+  elem->SetAttr(buzz::QN_NAME, candidate.name());
+  elem->SetAttr(QN_ADDRESS, candidate.address().IPAsString());
+  elem->SetAttr(QN_PORT, candidate.address().PortAsString());
+  elem->SetAttr(QN_PREFERENCE, candidate.preference_str());
+  elem->SetAttr(QN_USERNAME, candidate.username());
+  elem->SetAttr(QN_PROTOCOL, candidate.protocol());
+  elem->SetAttr(QN_GENERATION, candidate.generation_str());
+  if (candidate.password().size() > 0)
+    elem->SetAttr(QN_PASSWORD, candidate.password());
+  if (candidate.type().size() > 0)
+    elem->SetAttr(buzz::QN_TYPE, candidate.type());
+  if (candidate.network_name().size() > 0)
+    elem->SetAttr(QN_NETWORK, candidate.network_name());
+  return true;
+}
+
+TransportChannelImpl* P2PTransport::CreateTransportChannel(
+    const std::string& name, const std::string& content_type) {
+  return new P2PTransportChannel(name, content_type, this, port_allocator());
+}
+
+void P2PTransport::DestroyTransportChannel(TransportChannelImpl* channel) {
+  delete channel;
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/p2ptransport.h b/talk/p2p/base/p2ptransport.h
new file mode 100644
index 0000000..084f487
--- /dev/null
+++ b/talk/p2p/base/p2ptransport.h
@@ -0,0 +1,83 @@
+/*
+ * 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_P2P_BASE_P2PTRANSPORT_H_
+#define TALK_P2P_BASE_P2PTRANSPORT_H_
+
+#include <string>
+#include <vector>
+#include "talk/p2p/base/transport.h"
+
+namespace cricket {
+
+class P2PTransport: public Transport {
+ public:
+  P2PTransport(talk_base::Thread* signaling_thread,
+               talk_base::Thread* worker_thread,
+               PortAllocator* allocator);
+  virtual ~P2PTransport();
+
+  virtual void OnTransportError(const buzz::XmlElement* error);
+
+ protected:
+  // Creates and destroys P2PTransportChannel.
+  virtual TransportChannelImpl* CreateTransportChannel(
+      const std::string& name, const std::string& content_type);
+  virtual void DestroyTransportChannel(TransportChannelImpl* channel);
+
+  friend class P2PTransportChannel;
+
+  DISALLOW_EVIL_CONSTRUCTORS(P2PTransport);
+};
+
+class P2PTransportParser : public TransportParser {
+ public:
+  P2PTransportParser() {}
+  virtual bool ParseCandidates(SignalingProtocol protocol,
+                               const buzz::XmlElement* elem,
+                               Candidates* candidates,
+                               ParseError* error);
+  virtual bool WriteCandidates(SignalingProtocol protocol,
+                               const Candidates& candidates,
+                               XmlElements* candidate_elems,
+                               WriteError* error);
+ private:
+  bool ParseCandidate(const buzz::XmlElement* elem,
+                      Candidate* candidate,
+                      ParseError* error);
+  bool WriteCandidate(const Candidate& candidate,
+                      buzz::XmlElement* elem,
+                      WriteError* error);
+  bool VerifyUsernameFormat(const std::string& username,
+                            ParseError* error);
+
+  DISALLOW_EVIL_CONSTRUCTORS(P2PTransportParser);
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_P2PTRANSPORT_H_
diff --git a/talk/p2p/base/p2ptransportchannel.cc b/talk/p2p/base/p2ptransportchannel.cc
new file mode 100644
index 0000000..1b2bec2
--- /dev/null
+++ b/talk/p2p/base/p2ptransportchannel.cc
@@ -0,0 +1,926 @@
+/*
+ * 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/p2p/base/p2ptransportchannel.h"
+
+#include <set>
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/p2p/base/common.h"
+
+namespace {
+
+// messages for queuing up work for ourselves
+const uint32 MSG_SORT = 1;
+const uint32 MSG_PING = 2;
+const uint32 MSG_ALLOCATE = 3;
+
+// When the socket is unwritable, we will use 10 Kbps (ignoring IP+UDP headers)
+// for pinging.  When the socket is writable, we will use only 1 Kbps because
+// we don't want to degrade the quality on a modem.  These numbers should work
+// well on a 28.8K modem, which is the slowest connection on which the voice
+// quality is reasonable at all.
+static const uint32 PING_PACKET_SIZE = 60 * 8;
+static const uint32 WRITABLE_DELAY = 1000 * PING_PACKET_SIZE / 1000;  // 480ms
+static const uint32 UNWRITABLE_DELAY = 1000 * PING_PACKET_SIZE / 10000;  // 50ms
+
+// If there is a current writable connection, then we will also try hard to
+// make sure it is pinged at this rate.
+static const uint32 MAX_CURRENT_WRITABLE_DELAY = 900;  // 2*WRITABLE_DELAY - bit
+
+// The minimum improvement in RTT that justifies a switch.
+static const double kMinImprovement = 10;
+
+// Amount of time that we wait when *losing* writability before we try doing
+// another allocation.
+static const int kAllocateDelay = 1 * 1000;  // 1 second
+
+// We will try creating a new allocator from scratch after a delay of this
+// length without becoming writable (or timing out).
+static const int kAllocatePeriod = 20 * 1000;  // 20 seconds
+
+cricket::Port::CandidateOrigin GetOrigin(cricket::Port* port,
+                                         cricket::Port* origin_port) {
+  if (!origin_port)
+    return cricket::Port::ORIGIN_MESSAGE;
+  else if (port == origin_port)
+    return cricket::Port::ORIGIN_THIS_PORT;
+  else
+    return cricket::Port::ORIGIN_OTHER_PORT;
+}
+
+// Compares two connections based only on static information about them.
+int CompareConnectionCandidates(cricket::Connection* a,
+                                cricket::Connection* b) {
+  // Combine local and remote preferences
+  ASSERT(a->local_candidate().preference() == a->port()->preference());
+  ASSERT(b->local_candidate().preference() == b->port()->preference());
+  double a_pref = a->local_candidate().preference()
+                * a->remote_candidate().preference();
+  double b_pref = b->local_candidate().preference()
+                * b->remote_candidate().preference();
+
+  // Now check combined preferences. Lower values get sorted last.
+  if (a_pref > b_pref)
+    return 1;
+  if (a_pref < b_pref)
+    return -1;
+
+  return 0;
+}
+
+// Compare two connections based on their writability and static preferences.
+int CompareConnections(cricket::Connection *a, cricket::Connection *b) {
+  // Sort based on write-state.  Better states have lower values.
+  if (a->write_state() < b->write_state())
+    return 1;
+  if (a->write_state() > b->write_state())
+    return -1;
+
+  // Compare the candidate information.
+  return CompareConnectionCandidates(a, b);
+}
+
+// Wraps the comparison connection into a less than operator that puts higher
+// priority writable connections first.
+class ConnectionCompare {
+ public:
+  bool operator()(const cricket::Connection *ca,
+                  const cricket::Connection *cb) {
+    cricket::Connection* a = const_cast<cricket::Connection*>(ca);
+    cricket::Connection* b = const_cast<cricket::Connection*>(cb);
+
+    // Compare first on writability and static preferences.
+    int cmp = CompareConnections(a, b);
+    if (cmp > 0)
+      return true;
+    if (cmp < 0)
+      return false;
+
+    // Otherwise, sort based on latency estimate.
+    return a->rtt() < b->rtt();
+
+    // Should we bother checking for the last connection that last received
+    // data? It would help rendezvous on the connection that is also receiving
+    // packets.
+    //
+    // TODO: Yes we should definitely do this.  The TCP protocol gains
+    // efficiency by being used bidirectionally, as opposed to two separate
+    // unidirectional streams.  This test should probably occur before
+    // comparison of local prefs (assuming combined prefs are the same).  We
+    // need to be careful though, not to bounce back and forth with both sides
+    // trying to rendevous with the other.
+  }
+};
+
+// Determines whether we should switch between two connections, based first on
+// static preferences and then (if those are equal) on latency estimates.
+bool ShouldSwitch(cricket::Connection* a_conn, cricket::Connection* b_conn) {
+  if (a_conn == b_conn)
+    return false;
+
+  if (!a_conn || !b_conn)  // don't think the latter should happen
+    return true;
+
+  int prefs_cmp = CompareConnections(a_conn, b_conn);
+  if (prefs_cmp < 0)
+    return true;
+  if (prefs_cmp > 0)
+    return false;
+
+  return b_conn->rtt() <= a_conn->rtt() + kMinImprovement;
+}
+
+}  // unnamed namespace
+
+namespace cricket {
+
+P2PTransportChannel::P2PTransportChannel(const std::string &name,
+                                         const std::string &content_type,
+                                         P2PTransport* transport,
+                                         PortAllocator *allocator) :
+    TransportChannelImpl(name, content_type),
+    transport_(transport),
+    allocator_(allocator),
+    worker_thread_(talk_base::Thread::Current()),
+    waiting_for_signaling_(false),
+    error_(0),
+    best_connection_(NULL),
+    pinging_started_(false),
+    sort_dirty_(false),
+    was_writable_(false),
+    was_timed_out_(true) {
+}
+
+P2PTransportChannel::~P2PTransportChannel() {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+  for (uint32 i = 0; i < allocator_sessions_.size(); ++i)
+    delete allocator_sessions_[i];
+}
+
+// Add the allocator session to our list so that we know which sessions
+// are still active.
+void P2PTransportChannel::AddAllocatorSession(PortAllocatorSession* session) {
+  session->set_generation(static_cast<uint32>(allocator_sessions_.size()));
+  allocator_sessions_.push_back(session);
+
+  // We now only want to apply new candidates that we receive to the ports
+  // created by this new session because these are replacing those of the
+  // previous sessions.
+  ports_.clear();
+
+  session->SignalPortReady.connect(this, &P2PTransportChannel::OnPortReady);
+  session->SignalCandidatesReady.connect(
+      this, &P2PTransportChannel::OnCandidatesReady);
+  session->GetInitialPorts();
+  if (pinging_started_)
+    session->StartGetAllPorts();
+}
+
+// Go into the state of processing candidates, and running in general
+void P2PTransportChannel::Connect() {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+  // Kick off an allocator session
+  Allocate();
+
+  // Start pinging as the ports come in.
+  thread()->Post(this, MSG_PING);
+}
+
+// Reset the socket, clear up any previous allocations and start over
+void P2PTransportChannel::Reset() {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+  // Get rid of all the old allocators.  This should clean up everything.
+  for (uint32 i = 0; i < allocator_sessions_.size(); ++i)
+    delete allocator_sessions_[i];
+
+  allocator_sessions_.clear();
+  ports_.clear();
+  connections_.clear();
+  best_connection_ = NULL;
+
+  // Forget about all of the candidates we got before.
+  remote_candidates_.clear();
+
+  // Revert to the initial state.
+  set_readable(false);
+  set_writable(false);
+
+  // Reinitialize the rest of our state.
+  waiting_for_signaling_ = false;
+  pinging_started_ = false;
+  sort_dirty_ = false;
+  was_writable_ = false;
+  was_timed_out_ = true;
+
+  // If we allocated before, start a new one now.
+  if (transport_->connect_requested())
+    Allocate();
+
+  // Start pinging as the ports come in.
+  thread()->Clear(this);
+  thread()->Post(this, MSG_PING);
+}
+
+// A new port is available, attempt to make connections for it
+void P2PTransportChannel::OnPortReady(PortAllocatorSession *session,
+                                      Port* port) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+  // Set in-effect options on the new port
+  for (OptionMap::const_iterator it = options_.begin();
+       it != options_.end();
+       ++it) {
+    int val = port->SetOption(it->first, it->second);
+    if (val < 0) {
+      LOG_J(LS_WARNING, port) << "SetOption(" << it->first
+                              << ", " << it->second
+                              << ") failed: " << port->GetError();
+    }
+  }
+
+  // Remember the ports and candidates, and signal that candidates are ready.
+  // The session will handle this, and send an initiate/accept/modify message
+  // if one is pending.
+
+  ports_.push_back(port);
+  port->SignalUnknownAddress.connect(
+      this, &P2PTransportChannel::OnUnknownAddress);
+  port->SignalDestroyed.connect(this, &P2PTransportChannel::OnPortDestroyed);
+
+  // Attempt to create a connection from this new port to all of the remote
+  // candidates that we were given so far.
+
+  std::vector<RemoteCandidate>::iterator iter;
+  for (iter = remote_candidates_.begin(); iter != remote_candidates_.end();
+       ++iter)
+    CreateConnection(port, *iter, iter->origin_port(), false);
+
+  SortConnections();
+}
+
+// A new candidate is available, let listeners know
+void P2PTransportChannel::OnCandidatesReady(
+    PortAllocatorSession *session, const std::vector<Candidate>& candidates) {
+  for (size_t i = 0; i < candidates.size(); ++i) {
+    SignalCandidateReady(this, candidates[i]);
+  }
+}
+
+// Handle stun packets
+void P2PTransportChannel::OnUnknownAddress(
+    Port *port, const talk_base::SocketAddress &address, StunMessage *stun_msg,
+    const std::string &remote_username) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+  // Port has received a valid stun packet from an address that no Connection
+  // is currently available for. See if the remote user name is in the remote
+  // candidate list. If it isn't return error to the stun request.
+
+  const Candidate *candidate = NULL;
+  std::vector<RemoteCandidate>::iterator it;
+  for (it = remote_candidates_.begin(); it != remote_candidates_.end(); ++it) {
+    if ((*it).username() == remote_username) {
+      candidate = &(*it);
+      break;
+    }
+  }
+  if (candidate == NULL) {
+    // Don't know about this username, the request is bogus
+    // This sometimes happens if a binding response comes in before the ACCEPT
+    // message.  It is totally valid; the retry state machine will try again.
+
+    port->SendBindingErrorResponse(stun_msg, address,
+        STUN_ERROR_STALE_CREDENTIALS, STUN_ERROR_REASON_STALE_CREDENTIALS);
+    delete stun_msg;
+    return;
+  }
+
+  // Check for connectivity to this address. Create connections
+  // to this address across all local ports. First, add this as a new remote
+  // address
+
+  Candidate new_remote_candidate = *candidate;
+  new_remote_candidate.set_address(address);
+  // new_remote_candidate.set_protocol(port->protocol());
+
+  // This remote username exists. Now create connections using this candidate,
+  // and resort
+
+  if (CreateConnections(new_remote_candidate, port, true)) {
+    // Send the pinger a successful stun response.
+    port->SendBindingResponse(stun_msg, address);
+
+    // Update the list of connections since we just added another.  We do this
+    // after sending the response since it could (in principle) delete the
+    // connection in question.
+    SortConnections();
+  } else {
+    // Hopefully this won't occur, because changing a destination address
+    // shouldn't cause a new connection to fail
+    ASSERT(false);
+    port->SendBindingErrorResponse(stun_msg, address, STUN_ERROR_SERVER_ERROR,
+        STUN_ERROR_REASON_SERVER_ERROR);
+  }
+
+  delete stun_msg;
+}
+
+void P2PTransportChannel::OnCandidate(const Candidate& candidate) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+  // Create connections to this remote candidate.
+  CreateConnections(candidate, NULL, false);
+
+  // Resort the connections list, which may have new elements.
+  SortConnections();
+}
+
+// Creates connections from all of the ports that we care about to the given
+// remote candidate.  The return value is true if we created a connection from
+// the origin port.
+bool P2PTransportChannel::CreateConnections(const Candidate &remote_candidate,
+                                            Port* origin_port,
+                                            bool readable) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+  // Add a new connection for this candidate to every port that allows such a
+  // connection (i.e., if they have compatible protocols) and that does not
+  // already have a connection to an equivalent candidate.  We must be careful
+  // to make sure that the origin port is included, even if it was pruned,
+  // since that may be the only port that can create this connection.
+
+  bool created = false;
+
+  std::vector<Port *>::reverse_iterator it;
+  for (it = ports_.rbegin(); it != ports_.rend(); ++it) {
+    if (CreateConnection(*it, remote_candidate, origin_port, readable)) {
+      if (*it == origin_port)
+        created = true;
+    }
+  }
+
+  if ((origin_port != NULL) &&
+      std::find(ports_.begin(), ports_.end(), origin_port) == ports_.end()) {
+    if (CreateConnection(origin_port, remote_candidate, origin_port, readable))
+      created = true;
+  }
+
+  // Remember this remote candidate so that we can add it to future ports.
+  RememberRemoteCandidate(remote_candidate, origin_port);
+
+  return created;
+}
+
+// Setup a connection object for the local and remote candidate combination.
+// And then listen to connection object for changes.
+bool P2PTransportChannel::CreateConnection(Port* port,
+                                           const Candidate& remote_candidate,
+                                           Port* origin_port,
+                                           bool readable) {
+  // Look for an existing connection with this remote address.  If one is not
+  // found, then we can create a new connection for this address.
+  Connection* connection = port->GetConnection(remote_candidate.address());
+  if (connection != NULL) {
+    // It is not legal to try to change any of the parameters of an existing
+    // connection; however, the other side can send a duplicate candidate.
+    if (!remote_candidate.IsEquivalent(connection->remote_candidate())) {
+      LOG(INFO) << "Attempt to change a remote candidate";
+      return false;
+    }
+  } else {
+    Port::CandidateOrigin origin = GetOrigin(port, origin_port);
+    connection = port->CreateConnection(remote_candidate, origin);
+    if (!connection)
+      return false;
+
+    connections_.push_back(connection);
+    connection->SignalReadPacket.connect(
+        this, &P2PTransportChannel::OnReadPacket);
+    connection->SignalStateChange.connect(
+        this, &P2PTransportChannel::OnConnectionStateChange);
+    connection->SignalDestroyed.connect(
+        this, &P2PTransportChannel::OnConnectionDestroyed);
+
+    LOG_J(LS_INFO, this) << "Created connection with origin=" << origin << ", ("
+                         << connections_.size() << " total)";
+  }
+
+  // If we are readable, it is because we are creating this in response to a
+  // ping from the other side.  This will cause the state to become readable.
+  if (readable)
+    connection->ReceivedPing();
+
+  return true;
+}
+
+// Maintain our remote candidate list, adding this new remote one.
+void P2PTransportChannel::RememberRemoteCandidate(
+    const Candidate& remote_candidate, Port* origin_port) {
+  // Remove any candidates whose generation is older than this one.  The
+  // presence of a new generation indicates that the old ones are not useful.
+  uint32 i = 0;
+  while (i < remote_candidates_.size()) {
+    if (remote_candidates_[i].generation() < remote_candidate.generation()) {
+      LOG(INFO) << "Pruning candidate from old generation: "
+                << remote_candidates_[i].address().ToString();
+      remote_candidates_.erase(remote_candidates_.begin() + i);
+    } else {
+      i += 1;
+    }
+  }
+
+  // Make sure this candidate is not a duplicate.
+  for (uint32 i = 0; i < remote_candidates_.size(); ++i) {
+    if (remote_candidates_[i].IsEquivalent(remote_candidate)) {
+      LOG(INFO) << "Duplicate candidate: "
+                << remote_candidate.address().ToString();
+      return;
+    }
+  }
+
+  // Try this candidate for all future ports.
+  remote_candidates_.push_back(RemoteCandidate(remote_candidate, origin_port));
+
+  // We have some candidates from the other side, we are now serious about
+  // this connection.  Let's do the StartGetAllPorts thing.
+  if (!pinging_started_) {
+    pinging_started_ = true;
+    for (size_t i = 0; i < allocator_sessions_.size(); ++i) {
+      if (!allocator_sessions_[i]->IsGettingAllPorts())
+        allocator_sessions_[i]->StartGetAllPorts();
+    }
+  }
+}
+
+// Send data to the other side, using our best connection
+int P2PTransportChannel::SendPacket(const char *data, size_t len) {
+  // This can get called on any thread that is convenient to write from!
+  if (best_connection_ == NULL) {
+    error_ = EWOULDBLOCK;
+    return SOCKET_ERROR;
+  }
+  int sent = best_connection_->Send(data, len);
+  if (sent <= 0) {
+    ASSERT(sent < 0);
+    error_ = best_connection_->GetError();
+  }
+  return sent;
+}
+
+// Begin allocate (or immediately re-allocate, if MSG_ALLOCATE pending)
+void P2PTransportChannel::Allocate() {
+  CancelPendingAllocate();
+  // Time for a new allocator, lets make sure we have a signalling channel
+  // to communicate candidates through first.
+  waiting_for_signaling_ = true;
+  SignalRequestSignaling();
+}
+
+// Cancels the pending allocate, if any.
+void P2PTransportChannel::CancelPendingAllocate() {
+  thread()->Clear(this, MSG_ALLOCATE);
+}
+
+// Monitor connection states
+void P2PTransportChannel::UpdateConnectionStates() {
+  uint32 now = talk_base::Time();
+
+  // We need to copy the list of connections since some may delete themselves
+  // when we call UpdateState.
+  for (uint32 i = 0; i < connections_.size(); ++i)
+    connections_[i]->UpdateState(now);
+}
+
+// Prepare for best candidate sorting
+void P2PTransportChannel::RequestSort() {
+  if (!sort_dirty_) {
+    worker_thread_->Post(this, MSG_SORT);
+    sort_dirty_ = true;
+  }
+}
+
+// Sort the available connections to find the best one.  We also monitor
+// the number of available connections and the current state so that we
+// can possibly kick off more allocators (for more connections).
+void P2PTransportChannel::SortConnections() {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+  // Make sure the connection states are up-to-date since this affects how they
+  // will be sorted.
+  UpdateConnectionStates();
+
+  // Any changes after this point will require a re-sort.
+  sort_dirty_ = false;
+
+  // Get a list of the networks that we are using.
+  std::set<talk_base::Network*> networks;
+  for (uint32 i = 0; i < connections_.size(); ++i)
+    networks.insert(connections_[i]->port()->network());
+
+  // Find the best alternative connection by sorting.  It is important to note
+  // that amongst equal preference, writable connections, this will choose the
+  // one whose estimated latency is lowest.  So it is the only one that we
+  // need to consider switching to.
+
+  ConnectionCompare cmp;
+  std::stable_sort(connections_.begin(), connections_.end(), cmp);
+  Connection* top_connection = NULL;
+  if (connections_.size() > 0)
+    top_connection = connections_[0];
+
+  // If necessary, switch to the new choice.
+  if (ShouldSwitch(best_connection_, top_connection))
+    SwitchBestConnectionTo(top_connection);
+
+  // We can prune any connection for which there is a writable connection on
+  // the same network with better or equal prefences.  We leave those with
+  // better preference just in case they become writable later (at which point,
+  // we would prune out the current best connection).  We leave connections on
+  // other networks because they may not be using the same resources and they
+  // may represent very distinct paths over which we can switch.
+  std::set<talk_base::Network*>::iterator network;
+  for (network = networks.begin(); network != networks.end(); ++network) {
+    Connection* primier = GetBestConnectionOnNetwork(*network);
+    if (!primier || (primier->write_state() != Connection::STATE_WRITABLE))
+      continue;
+
+    for (uint32 i = 0; i < connections_.size(); ++i) {
+      if ((connections_[i] != primier) &&
+          (connections_[i]->port()->network() == *network) &&
+          (CompareConnectionCandidates(primier, connections_[i]) >= 0)) {
+        connections_[i]->Prune();
+      }
+    }
+  }
+
+  // Count the number of connections in the various states.
+
+  int writable = 0;
+  int write_connect = 0;
+  int write_timeout = 0;
+
+  for (uint32 i = 0; i < connections_.size(); ++i) {
+    switch (connections_[i]->write_state()) {
+    case Connection::STATE_WRITABLE:
+      ++writable;
+      break;
+    case Connection::STATE_WRITE_CONNECT:
+      ++write_connect;
+      break;
+    case Connection::STATE_WRITE_TIMEOUT:
+      ++write_timeout;
+      break;
+    default:
+      ASSERT(false);
+    }
+  }
+
+  if (writable > 0) {
+    HandleWritable();
+  } else if (write_connect > 0) {
+    HandleNotWritable();
+  } else {
+    HandleAllTimedOut();
+  }
+
+  // Update the state of this channel.  This method is called whenever the
+  // state of any connection changes, so this is a good place to do this.
+  UpdateChannelState();
+
+  // Notify of connection state change
+  SignalConnectionMonitor(this);
+}
+
+// Track the best connection, and let listeners know
+void P2PTransportChannel::SwitchBestConnectionTo(Connection* conn) {
+  // Note: if conn is NULL, the previous best_connection_ has been destroyed,
+  // so don't use it.
+  // use it.
+  Connection* old_best_connection = best_connection_;
+  best_connection_ = conn;
+  if (best_connection_) {
+    if (old_best_connection) {
+      LOG_J(LS_INFO, this) << "Previous best connection: "
+                           << old_best_connection->ToString();
+    }
+    LOG_J(LS_INFO, this) << "New best connection: "
+                         << best_connection_->ToString();
+    SignalRouteChange(this, best_connection_->remote_candidate().address());
+  } else {
+    LOG_J(LS_INFO, this) << "No best connection";
+  }
+}
+
+void P2PTransportChannel::UpdateChannelState() {
+  // The Handle* functions already set the writable state.  We'll just double-
+  // check it here.
+  bool writable = ((best_connection_ != NULL)  &&
+      (best_connection_->write_state() ==
+      Connection::STATE_WRITABLE));
+  ASSERT(writable == this->writable());
+  if (writable != this->writable())
+    LOG(LS_ERROR) << "UpdateChannelState: writable state mismatch";
+
+  bool readable = false;
+  for (uint32 i = 0; i < connections_.size(); ++i) {
+    if (connections_[i]->read_state() == Connection::STATE_READABLE)
+      readable = true;
+  }
+  set_readable(readable);
+}
+
+// We checked the status of our connections and we had at least one that
+// was writable, go into the writable state.
+void P2PTransportChannel::HandleWritable() {
+  //
+  // One or more connections writable!
+  //
+  if (!writable()) {
+    for (uint32 i = 0; i < allocator_sessions_.size(); ++i) {
+      if (allocator_sessions_[i]->IsGettingAllPorts()) {
+        allocator_sessions_[i]->StopGetAllPorts();
+      }
+    }
+
+    // Stop further allocations.
+    CancelPendingAllocate();
+  }
+
+  // We're writable, obviously we aren't timed out
+  was_writable_ = true;
+  was_timed_out_ = false;
+  set_writable(true);
+}
+
+// We checked the status of our connections and we didn't have any that
+// were writable, go into the connecting state (kick off a new allocator
+// session).
+void P2PTransportChannel::HandleNotWritable() {
+  //
+  // No connections are writable but not timed out!
+  //
+  if (was_writable_) {
+    // If we were writable, let's kick off an allocator session immediately
+    was_writable_ = false;
+    Allocate();
+  }
+
+  // We were connecting, obviously not ALL timed out.
+  was_timed_out_ = false;
+  set_writable(false);
+}
+
+// We checked the status of our connections and not only weren't they writable
+// but they were also timed out, we really need a new allocator.
+void P2PTransportChannel::HandleAllTimedOut() {
+  //
+  // No connections... all are timed out!
+  //
+  if (!was_timed_out_) {
+    // We weren't timed out before, so kick off an allocator now (we'll still
+    // be in the fully timed out state until the allocator actually gives back
+    // new ports)
+    Allocate();
+  }
+
+  // NOTE: we start was_timed_out_ in the true state so that we don't get
+  // another allocator created WHILE we are in the process of building up
+  // our first allocator.
+  was_timed_out_ = true;
+  was_writable_ = false;
+  set_writable(false);
+}
+
+// If we have a best connection, return it, otherwise return top one in the
+// list (later we will mark it best).
+Connection* P2PTransportChannel::GetBestConnectionOnNetwork(
+    talk_base::Network* network) {
+  // If the best connection is on this network, then it wins.
+  if (best_connection_ && (best_connection_->port()->network() == network))
+    return best_connection_;
+
+  // Otherwise, we return the top-most in sorted order.
+  for (uint32 i = 0; i < connections_.size(); ++i) {
+    if (connections_[i]->port()->network() == network)
+      return connections_[i];
+  }
+
+  return NULL;
+}
+
+// Handle any queued up requests
+void P2PTransportChannel::OnMessage(talk_base::Message *pmsg) {
+  if (pmsg->message_id == MSG_SORT)
+    OnSort();
+  else if (pmsg->message_id == MSG_PING)
+    OnPing();
+  else if (pmsg->message_id == MSG_ALLOCATE)
+    Allocate();
+  else
+    ASSERT(false);
+}
+
+// Handle queued up sort request
+void P2PTransportChannel::OnSort() {
+  // Resort the connections based on the new statistics.
+  SortConnections();
+}
+
+// Handle queued up ping request
+void P2PTransportChannel::OnPing() {
+  // Make sure the states of the connections are up-to-date (since this affects
+  // which ones are pingable).
+  UpdateConnectionStates();
+
+  // Find the oldest pingable connection and have it do a ping.
+  Connection* conn = FindNextPingableConnection();
+  if (conn)
+    conn->Ping(talk_base::Time());
+
+  // Post ourselves a message to perform the next ping.
+  uint32 delay = writable() ? WRITABLE_DELAY : UNWRITABLE_DELAY;
+  thread()->PostDelayed(delay, this, MSG_PING);
+}
+
+// Is the connection in a state for us to even consider pinging the other side?
+bool P2PTransportChannel::IsPingable(Connection* conn) {
+  // An unconnected connection cannot be written to at all, so pinging is out
+  // of the question.
+  if (!conn->connected())
+    return false;
+
+  if (writable()) {
+    // If we are writable, then we only want to ping connections that could be
+    // better than this one, i.e., the ones that were not pruned.
+    return (conn->write_state() != Connection::STATE_WRITE_TIMEOUT);
+  } else {
+    // If we are not writable, then we need to try everything that might work.
+    // This includes both connections that do not have write timeout as well as
+    // ones that do not have read timeout.  A connection could be readable but
+    // be in write-timeout if we pruned it before.  Since the other side is
+    // still pinging it, it very well might still work.
+    return (conn->write_state() != Connection::STATE_WRITE_TIMEOUT) ||
+           (conn->read_state() != Connection::STATE_READ_TIMEOUT);
+  }
+}
+
+// Returns the next pingable connection to ping.  This will be the oldest
+// pingable connection unless we have a writable connection that is past the
+// maximum acceptable ping delay.
+Connection* P2PTransportChannel::FindNextPingableConnection() {
+  uint32 now = talk_base::Time();
+  if (best_connection_ &&
+      (best_connection_->write_state() == Connection::STATE_WRITABLE) &&
+      (best_connection_->last_ping_sent()
+       + MAX_CURRENT_WRITABLE_DELAY <= now)) {
+    return best_connection_;
+  }
+
+  Connection* oldest_conn = NULL;
+  uint32 oldest_time = 0xFFFFFFFF;
+  for (uint32 i = 0; i < connections_.size(); ++i) {
+    if (IsPingable(connections_[i])) {
+      if (connections_[i]->last_ping_sent() < oldest_time) {
+        oldest_time = connections_[i]->last_ping_sent();
+        oldest_conn = connections_[i];
+      }
+    }
+  }
+  return oldest_conn;
+}
+
+// return the number of "pingable" connections
+uint32 P2PTransportChannel::NumPingableConnections() {
+  uint32 count = 0;
+  for (uint32 i = 0; i < connections_.size(); ++i) {
+    if (IsPingable(connections_[i]))
+      count += 1;
+  }
+  return count;
+}
+
+// When a connection's state changes, we need to figure out who to use as
+// the best connection again.  It could have become usable, or become unusable.
+void P2PTransportChannel::OnConnectionStateChange(Connection *connection) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+  // We have to unroll the stack before doing this because we may be changing
+  // the state of connections while sorting.
+  RequestSort();
+}
+
+// When a connection is removed, edit it out, and then update our best
+// connection.
+void P2PTransportChannel::OnConnectionDestroyed(Connection *connection) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+  // Note: the previous best_connection_ may be destroyed by now, so don't
+  // use it.
+
+  // Remove this connection from the list.
+  std::vector<Connection*>::iterator iter =
+      std::find(connections_.begin(), connections_.end(), connection);
+  ASSERT(iter != connections_.end());
+  connections_.erase(iter);
+
+  LOG_J(LS_INFO, this) << "Removed connection ("
+    << static_cast<int>(connections_.size()) << " remaining)";
+
+  // If this is currently the best connection, then we need to pick a new one.
+  // The call to SortConnections will pick a new one.  It looks at the current
+  // best connection in order to avoid switching between fairly similar ones.
+  // Since this connection is no longer an option, we can just set best to NULL
+  // and re-choose a best assuming that there was no best connection.
+  if (best_connection_ == connection) {
+    SwitchBestConnectionTo(NULL);
+    RequestSort();
+  }
+}
+
+// When a port is destroyed remove it from our list of ports to use for
+// connection attempts.
+void P2PTransportChannel::OnPortDestroyed(Port* port) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+  // Remove this port from the list (if we didn't drop it already).
+  std::vector<Port*>::iterator iter =
+      std::find(ports_.begin(), ports_.end(), port);
+  if (iter != ports_.end())
+    ports_.erase(iter);
+
+  LOG(INFO) << "Removed port from p2p socket: "
+            << static_cast<int>(ports_.size()) << " remaining";
+}
+
+// We data is available, let listeners know
+void P2PTransportChannel::OnReadPacket(Connection *connection,
+                                       const char *data, size_t len) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+  // Let the client know of an incoming packet
+
+  SignalReadPacket(this, data, len);
+}
+
+// Set options on ourselves is simply setting options on all of our available
+// port objects.
+int P2PTransportChannel::SetOption(talk_base::Socket::Option opt, int value) {
+  OptionMap::iterator it = options_.find(opt);
+  if (it == options_.end()) {
+    options_.insert(std::make_pair(opt, value));
+  } else if (it->second == value) {
+    return 0;
+  } else {
+    it->second = value;
+  }
+
+  for (uint32 i = 0; i < ports_.size(); ++i) {
+    int val = ports_[i]->SetOption(opt, value);
+    if (val < 0) {
+      // Because this also occurs deferred, probably no point in reporting an
+      // error
+      LOG(WARNING) << "SetOption(" << opt << ", " << value << ") failed: "
+                   << ports_[i]->GetError();
+    }
+  }
+  return 0;
+}
+
+// When the signalling channel is ready, we can really kick off the allocator
+void P2PTransportChannel::OnSignalingReady() {
+  if (waiting_for_signaling_) {
+    waiting_for_signaling_ = false;
+    AddAllocatorSession(allocator_->CreateSession(name(), content_type()));
+    thread()->PostDelayed(kAllocatePeriod, this, MSG_ALLOCATE);
+  }
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/p2ptransportchannel.h b/talk/p2p/base/p2ptransportchannel.h
new file mode 100644
index 0000000..805e159
--- /dev/null
+++ b/talk/p2p/base/p2ptransportchannel.h
@@ -0,0 +1,164 @@
+/*
+ * 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.
+ */
+
+// P2PTransportChannel wraps up the state management of the connection between
+// two P2P clients.  Clients have candidate ports for connecting, and
+// connections which are combinations of candidates from each end (Alice and
+// Bob each have candidates, one candidate from Alice and one candidate from
+// Bob are used to make a connection, repeat to make many connections).
+//
+// When all of the available connections become invalid (non-writable), we
+// kick off a process of determining more candidates and more connections.
+//
+#ifndef TALK_P2P_BASE_P2PTRANSPORTCHANNEL_H_
+#define TALK_P2P_BASE_P2PTRANSPORTCHANNEL_H_
+
+#include <map>
+#include <vector>
+#include <string>
+#include "talk/base/sigslot.h"
+#include "talk/p2p/base/candidate.h"
+#include "talk/p2p/base/port.h"
+#include "talk/p2p/base/portallocator.h"
+#include "talk/p2p/base/transport.h"
+#include "talk/p2p/base/transportchannelimpl.h"
+#include "talk/p2p/base/p2ptransport.h"
+
+namespace cricket {
+
+// Adds the port on which the candidate originated.
+class RemoteCandidate : public Candidate {
+ public:
+  RemoteCandidate(const Candidate& c, Port* origin_port)
+    : Candidate(c), origin_port_(origin_port) {}
+
+  Port* origin_port() { return origin_port_; }
+
+ private:
+  Port* origin_port_;
+};
+
+// P2PTransportChannel manages the candidates and connection process to keep
+// two P2P clients connected to each other.
+class P2PTransportChannel : public TransportChannelImpl,
+    public talk_base::MessageHandler {
+ public:
+  P2PTransportChannel(const std::string &name,
+                      const std::string &content_type,
+                      P2PTransport* transport,
+                      PortAllocator *allocator);
+  virtual ~P2PTransportChannel();
+
+  // From TransportChannelImpl:
+  virtual Transport* GetTransport() { return transport_; }
+  virtual void Connect();
+  virtual void Reset();
+  virtual void OnSignalingReady();
+
+  // From TransportChannel:
+  virtual int SendPacket(const char *data, size_t len);
+  virtual int SetOption(talk_base::Socket::Option opt, int value);
+  virtual int GetError() { return error_; }
+
+  // This hack is here to allow the SocketMonitor to downcast to the
+  // P2PTransportChannel safely.
+  virtual P2PTransportChannel* GetP2PChannel() { return this; }
+
+  // These are used by the connection monitor.
+  sigslot::signal1<P2PTransportChannel*> SignalConnectionMonitor;
+  const std::vector<Connection *>& connections() const { return connections_; }
+  Connection* best_connection() const { return best_connection_; }
+
+  // Handler for internal messages.
+  virtual void OnMessage(talk_base::Message *pmsg);
+
+  virtual void OnCandidate(const Candidate& candidate);
+
+ private:
+  void Allocate();
+  void CancelPendingAllocate();
+  void UpdateConnectionStates();
+  void RequestSort();
+  void SortConnections();
+  void SwitchBestConnectionTo(Connection* conn);
+  void UpdateChannelState();
+  void HandleWritable();
+  void HandleNotWritable();
+  void HandleAllTimedOut();
+  Connection* GetBestConnectionOnNetwork(talk_base::Network* network);
+  bool CreateConnections(const Candidate &remote_candidate, Port* origin_port,
+                         bool readable);
+  bool CreateConnection(Port* port, const Candidate& remote_candidate,
+                        Port* origin_port, bool readable);
+  void RememberRemoteCandidate(const Candidate& remote_candidate,
+                               Port* origin_port);
+  void OnUnknownAddress(Port *port, const talk_base::SocketAddress &addr,
+                        StunMessage *stun_msg,
+                        const std::string &remote_username);
+  void OnPortReady(PortAllocatorSession *session, Port* port);
+  void OnCandidatesReady(PortAllocatorSession *session,
+                         const std::vector<Candidate>& candidates);
+  void OnConnectionStateChange(Connection *connection);
+  void OnConnectionDestroyed(Connection *connection);
+  void OnPortDestroyed(Port* port);
+  void OnReadPacket(Connection *connection, const char *data, size_t len);
+  void OnSort();
+  void OnPing();
+  bool IsPingable(Connection* conn);
+  Connection* FindNextPingableConnection();
+  uint32 NumPingableConnections();
+  PortAllocatorSession* allocator_session() {
+    return allocator_sessions_.back();
+  }
+  void AddAllocatorSession(PortAllocatorSession* session);
+
+  talk_base::Thread* thread() const { return worker_thread_; }
+
+  P2PTransport* transport_;
+  PortAllocator *allocator_;
+  talk_base::Thread *worker_thread_;
+  bool waiting_for_signaling_;
+  int error_;
+  std::vector<PortAllocatorSession*> allocator_sessions_;
+  std::vector<Port *> ports_;
+  std::vector<Connection *> connections_;
+  Connection *best_connection_;
+  std::vector<RemoteCandidate> remote_candidates_;
+  // indicates whether StartGetAllCandidates has been called
+  bool pinging_started_;
+  bool sort_dirty_;  // indicates whether another sort is needed right now
+  bool was_writable_;
+  bool was_timed_out_;
+  typedef std::map<talk_base::Socket::Option, int> OptionMap;
+  OptionMap options_;
+
+  DISALLOW_EVIL_CONSTRUCTORS(P2PTransportChannel);
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_P2PTRANSPORTCHANNEL_H_
diff --git a/talk/p2p/base/parsing.cc b/talk/p2p/base/parsing.cc
new file mode 100644
index 0000000..da4c31b
--- /dev/null
+++ b/talk/p2p/base/parsing.cc
@@ -0,0 +1,163 @@
+/*
+ * 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 "talk/p2p/base/parsing.h"
+
+#include <algorithm>
+#include <stdlib.h>
+#include "talk/base/stringutils.h"
+
+namespace {
+std::string kTrue = "true";
+std::string kOne = "1";
+}
+
+namespace cricket {
+
+bool BadParse(const std::string& text, ParseError* err) {
+  if (err != NULL) {
+    err->text = text;
+  }
+  return false;
+}
+
+bool BadWrite(const std::string& text, WriteError* err) {
+  if (err != NULL) {
+    err->text = text;
+  }
+  return false;
+}
+
+std::string GetXmlAttr(const buzz::XmlElement* elem,
+                       const buzz::QName& name,
+                       const std::string& def) {
+  std::string val = elem->Attr(name);
+  return val.empty() ? def : val;
+}
+
+std::string GetXmlAttr(const buzz::XmlElement* elem,
+                       const buzz::QName& name,
+                       const char* def) {
+    return GetXmlAttr(elem, name, std::string(def));
+}
+
+bool GetXmlAttr(const buzz::XmlElement* elem,
+                const buzz::QName& name, bool def) {
+  std::string val = elem->Attr(name);
+  std::transform(val.begin(), val.end(), val.begin(), tolower);
+
+  return val.empty() ? def : (val == kTrue || val == kOne);
+}
+
+int GetXmlAttr(const buzz::XmlElement* elem,
+               const buzz::QName& name, int def) {
+  std::string val = elem->Attr(name);
+  return val.empty() ? def : atoi(val.c_str());
+}
+
+void AddXmlAttr(buzz::XmlElement* elem,
+                const buzz::QName& name, int n) {
+  char buf[32];
+  talk_base::sprintfn(buf, sizeof(buf), "%d", n);
+  elem->AddAttr(name, buf);
+}
+
+void SetXmlBody(buzz::XmlElement* elem, uint32 u) {
+  char buf[16];
+  talk_base::sprintfn(buf, sizeof(buf), "%u", u);
+  elem->SetBodyText(buf);
+}
+
+const buzz::XmlElement* GetXmlChild(const buzz::XmlElement* parent,
+                                    const std::string& name) {
+  for (const buzz::XmlElement* child = parent->FirstElement();
+       child != NULL;
+       child = child->NextElement()) {
+    if (child->Name().LocalPart() == name) {
+      return child;
+    }
+  }
+  return NULL;
+}
+
+bool RequireXmlChild(const buzz::XmlElement* parent,
+                     const std::string& name,
+                     const buzz::XmlElement** child,
+                     ParseError* error) {
+  *child = GetXmlChild(parent, name);
+  if (*child == NULL) {
+    return BadParse("element '" + parent->Name().Merged() +
+                    "' missing required child '" + name,
+                    error);
+  } else {
+    return true;
+  }
+}
+
+bool RequireXmlAttr(const buzz::XmlElement* elem,
+                    const buzz::QName& name,
+                    std::string* value,
+                    ParseError* error) {
+  if (!elem->HasAttr(name)) {
+    return BadParse("element '" + elem->Name().Merged() +
+                    "' missing required attribute '"
+                    + name.Merged() + "'",
+                    error);
+  } else {
+    *value = elem->Attr(name);
+    return true;
+  }
+}
+
+void AddXmlChildren(buzz::XmlElement* parent,
+                    const std::vector<buzz::XmlElement*>& children) {
+  for (std::vector<buzz::XmlElement*>::const_iterator iter = children.begin();
+       iter != children.end();
+       iter++) {
+    parent->AddElement(*iter);
+  }
+}
+
+void CopyXmlChildren(const buzz::XmlElement* source, buzz::XmlElement* dest) {
+  for (const buzz::XmlElement* child = source->FirstElement();
+       child != NULL;
+       child = child->NextElement()) {
+    dest->AddElement(new buzz::XmlElement(*child));
+  }
+}
+
+std::vector<buzz::XmlElement*> CopyOfXmlChildren(const buzz::XmlElement* elem) {
+  std::vector<buzz::XmlElement*> children;
+  for (const buzz::XmlElement* child = elem->FirstElement();
+       child != NULL;
+       child = child->NextElement()) {
+    children.push_back(new buzz::XmlElement(*child));
+  }
+  return children;
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/parsing.h b/talk/p2p/base/parsing.h
new file mode 100644
index 0000000..805372b
--- /dev/null
+++ b/talk/p2p/base/parsing.h
@@ -0,0 +1,108 @@
+/*
+ * 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.
+ */
+
+#ifndef TALK_P2P_BASE_PARSING_H_
+#define TALK_P2P_BASE_PARSING_H_
+
+#include <string>
+#include <vector>
+#include "talk/base/basictypes.h"
+#include "talk/xmllite/xmlelement.h"  // Needed to delete ParseError.extra.
+
+namespace cricket {
+
+// We decided "bool Parse(in, out*, error*)" is generally the best
+// parse signature.  "out Parse(in)" doesn't allow for errors.
+// "error* Parse(in, out*)" doesn't allow flexible memory management.
+
+// The error type for parsing.
+struct ParseError {
+ public:
+  // explains the error
+  std::string text;
+  // provide details about what wasn't parsable
+  const buzz::XmlElement* extra;
+
+  ParseError() : extra(NULL) {}
+
+  ~ParseError() {
+    delete extra;
+  }
+
+  void SetText(const std::string& text) {
+    this->text = text;
+  }
+};
+
+// The error type for writing.
+struct WriteError {
+  std::string text;
+
+  void SetText(const std::string& text) {
+    this->text = text;
+  }
+};
+
+// Convenience method for returning a message when parsing fails.
+bool BadParse(const std::string& text, ParseError* err);
+
+// Convenience method for returning a message when writing fails.
+bool BadWrite(const std::string& text, WriteError* error);
+
+// helper XML functions
+std::string GetXmlAttr(const buzz::XmlElement* elem,
+                       const buzz::QName& name,
+                       const std::string& def);
+std::string GetXmlAttr(const buzz::XmlElement* elem,
+                       const buzz::QName& name,
+                       const char* def);
+// Return true if the value is "true" or "1".
+bool GetXmlAttr(const buzz::XmlElement* elem,
+                const buzz::QName& name, bool def);
+int GetXmlAttr(const buzz::XmlElement* elem,
+               const buzz::QName& name, int def);
+void AddXmlAttr(buzz::XmlElement* elem,
+                const buzz::QName& name, int n);
+void SetXmlBody(buzz::XmlElement* elem, uint32 u);
+const buzz::XmlElement* GetXmlChild(const buzz::XmlElement* parent,
+                                    const std::string& name);
+bool RequireXmlChild(const buzz::XmlElement* parent,
+                     const std::string& name,
+                     const buzz::XmlElement** child,
+                     ParseError* error);
+bool RequireXmlAttr(const buzz::XmlElement* elem,
+                    const buzz::QName& name,
+                    std::string* value,
+                    ParseError* error);
+void AddXmlChildren(buzz::XmlElement* parent,
+                    const std::vector<buzz::XmlElement*>& children);
+void CopyXmlChildren(const buzz::XmlElement* source, buzz::XmlElement* dest);
+std::vector<buzz::XmlElement*> CopyOfXmlChildren(const buzz::XmlElement* elem);
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_PARSING_H_
diff --git a/talk/p2p/base/port.cc b/talk/p2p/base/port.cc
new file mode 100644
index 0000000..0768e5b
--- /dev/null
+++ b/talk/p2p/base/port.cc
@@ -0,0 +1,895 @@
+/*
+ * 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/p2p/base/port.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stringutils.h"
+#include "talk/p2p/base/common.h"
+
+namespace {
+
+// The length of time we wait before timing out readability on a connection.
+const uint32 CONNECTION_READ_TIMEOUT = 30 * 1000;   // 30 seconds
+
+// The length of time we wait before timing out writability on a connection.
+const uint32 CONNECTION_WRITE_TIMEOUT = 15 * 1000;  // 15 seconds
+
+// The length of time we wait before we become unwritable.
+const uint32 CONNECTION_WRITE_CONNECT_TIMEOUT = 5 * 1000;  // 5 seconds
+
+// The number of pings that must fail to respond before we become unwritable.
+const uint32 CONNECTION_WRITE_CONNECT_FAILURES = 5;
+
+// This is the length of time that we wait for a ping response to come back.
+const int CONNECTION_RESPONSE_TIMEOUT = 5 * 1000;   // 5 seconds
+
+// Determines whether we have seen at least the given maximum number of
+// pings fail to have a response.
+inline bool TooManyFailures(
+    const std::vector<uint32>& pings_since_last_response,
+    uint32 maximum_failures,
+    uint32 rtt_estimate,
+    uint32 now) {
+
+  // If we haven't sent that many pings, then we can't have failed that many.
+  if (pings_since_last_response.size() < maximum_failures)
+    return false;
+
+  // Check if the window in which we would expect a response to the ping has
+  // already elapsed.
+  return pings_since_last_response[maximum_failures - 1] + rtt_estimate < now;
+}
+
+// Determines whether we have gone too long without seeing any response.
+inline bool TooLongWithoutResponse(
+    const std::vector<uint32>& pings_since_last_response,
+    uint32 maximum_time,
+    uint32 now) {
+
+  if (pings_since_last_response.size() == 0)
+    return false;
+
+  return pings_since_last_response[0] + maximum_time < now;
+}
+
+// We will restrict RTT estimates (when used for determining state) to be
+// within a reasonable range.
+const uint32 MINIMUM_RTT = 100;   // 0.1 seconds
+const uint32 MAXIMUM_RTT = 3000;  // 3 seconds
+
+// When we don't have any RTT data, we have to pick something reasonable.  We
+// use a large value just in case the connection is really slow.
+const uint32 DEFAULT_RTT = MAXIMUM_RTT;
+
+// Computes our estimate of the RTT given the current estimate.
+inline uint32 ConservativeRTTEstimate(uint32 rtt) {
+  return talk_base::_max(MINIMUM_RTT, talk_base::_min(MAXIMUM_RTT, 2 * rtt));
+}
+
+// Weighting of the old rtt value to new data.
+const int RTT_RATIO = 3;  // 3 : 1
+
+// The delay before we begin checking if this port is useless.
+const int kPortTimeoutDelay = 30 * 1000;  // 30 seconds
+
+const uint32 MSG_CHECKTIMEOUT = 1;
+const uint32 MSG_DELETE = 1;
+}
+
+namespace cricket {
+
+static const char* const PROTO_NAMES[] = { "udp", "tcp", "ssltcp" };
+
+const char* ProtoToString(ProtocolType proto) {
+  return PROTO_NAMES[proto];
+}
+
+bool StringToProto(const char* value, ProtocolType* proto) {
+  for (size_t i = 0; i <= PROTO_LAST; ++i) {
+    if (strcmp(PROTO_NAMES[i], value) == 0) {
+      *proto = static_cast<ProtocolType>(i);
+      return true;
+    }
+  }
+  return false;
+}
+
+Port::Port(talk_base::Thread* thread, const std::string& type,
+           talk_base::PacketSocketFactory* factory, talk_base::Network* network,
+           uint32 ip, int min_port, int max_port)
+    : thread_(thread),
+      factory_(factory),
+      type_(type),
+      network_(network),
+      ip_(ip),
+      min_port_(min_port),
+      max_port_(max_port),
+      preference_(-1),
+      lifetime_(LT_PRESTART),
+      enable_port_packets_(false) {
+  ASSERT(factory_ != NULL);
+
+  set_username_fragment(talk_base::CreateRandomString(16));
+  set_password(talk_base::CreateRandomString(16));
+  LOG_J(LS_INFO, this) << "Port created";
+}
+
+Port::~Port() {
+  // Delete all of the remaining connections.  We copy the list up front
+  // because each deletion will cause it to be modified.
+
+  std::vector<Connection*> list;
+
+  AddressMap::iterator iter = connections_.begin();
+  while (iter != connections_.end()) {
+    list.push_back(iter->second);
+    ++iter;
+  }
+
+  for (uint32 i = 0; i < list.size(); i++)
+    delete list[i];
+}
+
+Connection* Port::GetConnection(const talk_base::SocketAddress& remote_addr) {
+  AddressMap::const_iterator iter = connections_.find(remote_addr);
+  if (iter != connections_.end())
+    return iter->second;
+  else
+    return NULL;
+}
+
+void Port::AddAddress(const talk_base::SocketAddress& address,
+                      const std::string& protocol,
+                      bool final) {
+  Candidate c;
+  c.set_name(name_);
+  c.set_type(type_);
+  c.set_protocol(protocol);
+  c.set_address(address);
+  c.set_preference(preference_);
+  c.set_username(username_frag_);
+  c.set_password(password_);
+  c.set_network_name(network_->name());
+  c.set_generation(generation_);
+  candidates_.push_back(c);
+
+  if (final)
+    SignalAddressReady(this);
+}
+
+void Port::AddConnection(Connection* conn) {
+  connections_[conn->remote_candidate().address()] = conn;
+  conn->SignalDestroyed.connect(this, &Port::OnConnectionDestroyed);
+  SignalConnectionCreated(this, conn);
+}
+
+void Port::OnReadPacket(
+    const char* data, size_t size, const talk_base::SocketAddress& addr) {
+  // If the user has enabled port packets, just hand this over.
+  if (enable_port_packets_) {
+    SignalReadPacket(this, data, size, addr);
+    return;
+  }
+
+  // If this is an authenticated STUN request, then signal unknown address and
+  // send back a proper binding response.
+  StunMessage* msg;
+  std::string remote_username;
+  if (!GetStunMessage(data, size, addr, &msg, &remote_username)) {
+    LOG_J(LS_ERROR, this) << "Received non-STUN packet from unknown address ("
+                          << addr.ToString() << ")";
+  } else if (!msg) {
+    // STUN message handled already
+  } else if (msg->type() == STUN_BINDING_REQUEST) {
+    SignalUnknownAddress(this, addr, msg, remote_username);
+  } else {
+    // NOTE(tschmelcher): This is benign. It occurs if we pruned a
+    // connection for this port while it had STUN requests in flight, because
+    // we then get back responses for them, which this code correctly does not
+    // handle.
+    LOG_J(LS_ERROR, this) << "Received unexpected STUN message type ("
+                          << msg->type() << ") from unknown address ("
+                          << addr.ToString() << ")";
+    delete msg;
+  }
+}
+
+bool Port::GetStunMessage(const char* data, size_t size,
+                          const talk_base::SocketAddress& addr,
+                          StunMessage** out_msg, std::string* out_username) {
+  // NOTE: This could clearly be optimized to avoid allocating any memory.
+  //       However, at the data rates we'll be looking at on the client side,
+  //       this probably isn't worth worrying about.
+  ASSERT(out_msg != NULL);
+  ASSERT(out_username != NULL);
+  *out_msg = NULL;
+  out_username->clear();
+
+  // Parse the request message.  If the packet is not a complete and correct
+  // STUN message, then ignore it.
+  talk_base::scoped_ptr<StunMessage> stun_msg(new StunMessage());
+  talk_base::ByteBuffer buf(data, size);
+  if (!stun_msg->Read(&buf) || (buf.Length() > 0)) {
+    return false;
+  }
+
+  // The packet must include a username that either begins or ends with our
+  // fragment.  It should begin with our fragment if it is a request and it
+  // should end with our fragment if it is a response.
+  const StunByteStringAttribute* username_attr =
+      stun_msg->GetByteString(STUN_ATTR_USERNAME);
+
+  int remote_frag_len = (username_attr ? username_attr->length() : 0);
+  remote_frag_len -= static_cast<int>(username_frag_.size());
+
+  if (stun_msg->type() == STUN_BINDING_REQUEST) {
+    if (remote_frag_len < 0) {
+      // Username not present or corrupted, don't reply.
+      LOG_J(LS_ERROR, this) << "Received STUN request without username from "
+                            << addr.ToString();
+      return true;
+    } else if (std::memcmp(username_attr->bytes(), username_frag_.c_str(),
+                           username_frag_.size()) != 0) {
+      LOG_J(LS_ERROR, this) << "Received STUN request with bad local username "
+                            << std::string(username_attr->bytes(),
+                                           username_attr->length()) << " from "
+                            << addr.ToString();
+      SendBindingErrorResponse(stun_msg.get(), addr, STUN_ERROR_BAD_REQUEST,
+                               STUN_ERROR_REASON_BAD_REQUEST);
+      return true;
+    }
+
+    out_username->assign(username_attr->bytes() + username_frag_.size(),
+                         username_attr->bytes() + username_attr->length());
+  } else if ((stun_msg->type() == STUN_BINDING_RESPONSE)
+      || (stun_msg->type() == STUN_BINDING_ERROR_RESPONSE)) {
+    if (remote_frag_len < 0) {
+      LOG_J(LS_ERROR, this) << "Received STUN response without username from "
+                            << addr.ToString();
+      // Do not send error response to a response
+      return true;
+    } else if (std::memcmp(username_attr->bytes() + remote_frag_len,
+                           username_frag_.c_str(),
+                           username_frag_.size()) != 0) {
+      LOG_J(LS_ERROR, this) << "Received STUN response with bad local username "
+                            << std::string(username_attr->bytes(),
+                                           username_attr->length()) << " from "
+                            << addr.ToString();
+      // Do not send error response to a response
+      return true;
+    }
+
+    out_username->assign(username_attr->bytes(),
+                         username_attr->bytes() + remote_frag_len);
+
+    if (stun_msg->type() == STUN_BINDING_ERROR_RESPONSE) {
+      if (const StunErrorCodeAttribute* error_code = stun_msg->GetErrorCode()) {
+        LOG_J(LS_ERROR, this) << "Received STUN binding error:"
+                              << " class="
+                              << static_cast<int>(error_code->error_class())
+                              << " number="
+                              << static_cast<int>(error_code->number())
+                              << " reason='" << error_code->reason() << "'"
+                              << " from " << addr.ToString();
+        // Return message to allow error-specific processing
+      } else {
+        LOG_J(LS_ERROR, this) << "Received STUN binding error without a error "
+                              << "code from " << addr.ToString();
+        // Drop corrupt message
+        return true;
+      }
+    }
+  } else {
+    LOG_J(LS_ERROR, this) << "Received STUN packet with invalid type ("
+                          << stun_msg->type() << ") from " << addr.ToString();
+    return true;
+  }
+
+  // Return the STUN message found.
+  *out_msg = stun_msg.release();
+  return true;
+}
+
+void Port::SendBindingResponse(StunMessage* request,
+                               const talk_base::SocketAddress& addr) {
+  ASSERT(request->type() == STUN_BINDING_REQUEST);
+
+  // Retrieve the username from the request.
+  const StunByteStringAttribute* username_attr =
+      request->GetByteString(STUN_ATTR_USERNAME);
+  ASSERT(username_attr != NULL);
+  if (username_attr == NULL) {
+    // No valid username, skip the response.
+    return;
+  }
+
+  // Fill in the response message.
+  StunMessage response;
+  response.SetType(STUN_BINDING_RESPONSE);
+  response.SetTransactionID(request->transaction_id());
+
+  StunByteStringAttribute* username2_attr =
+      StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
+  username2_attr->CopyBytes(username_attr->bytes(), username_attr->length());
+  response.AddAttribute(username2_attr);
+
+  StunAddressAttribute* addr_attr =
+      StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS);
+  addr_attr->SetFamily(1);
+  addr_attr->SetPort(addr.port());
+  addr_attr->SetIP(addr.ip());
+  response.AddAttribute(addr_attr);
+
+  // Send the response message.
+  // NOTE: If we wanted to, this is where we would add the HMAC.
+  talk_base::ByteBuffer buf;
+  response.Write(&buf);
+  if (SendTo(buf.Data(), buf.Length(), addr, false) < 0) {
+    LOG_J(LS_ERROR, this) << "Failed to send STUN ping response to "
+                          << addr.ToString();
+  }
+
+  // The fact that we received a successful request means that this connection
+  // (if one exists) should now be readable.
+  Connection* conn = GetConnection(addr);
+  ASSERT(conn != NULL);
+  if (conn)
+    conn->ReceivedPing();
+}
+
+void Port::SendBindingErrorResponse(StunMessage* request,
+                                    const talk_base::SocketAddress& addr,
+                                    int error_code, const std::string& reason) {
+  ASSERT(request->type() == STUN_BINDING_REQUEST);
+
+  // Retrieve the username from the request. If it didn't have one, we
+  // shouldn't be responding at all.
+  const StunByteStringAttribute* username_attr =
+      request->GetByteString(STUN_ATTR_USERNAME);
+  ASSERT(username_attr != NULL);
+  if (username_attr == NULL) {
+    // No valid username, skip the response.
+    return;
+  }
+
+  // Fill in the response message.
+  StunMessage response;
+  response.SetType(STUN_BINDING_ERROR_RESPONSE);
+  response.SetTransactionID(request->transaction_id());
+
+  StunByteStringAttribute* username2_attr =
+      StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
+  username2_attr->CopyBytes(username_attr->bytes(), username_attr->length());
+  response.AddAttribute(username2_attr);
+
+  StunErrorCodeAttribute* error_attr = StunAttribute::CreateErrorCode();
+  error_attr->SetErrorCode(error_code);
+  error_attr->SetReason(reason);
+  response.AddAttribute(error_attr);
+
+  // Send the response message.
+  // NOTE: If we wanted to, this is where we would add the HMAC.
+  talk_base::ByteBuffer buf;
+  response.Write(&buf);
+  SendTo(buf.Data(), buf.Length(), addr, false);
+  LOG_J(LS_INFO, this) << "Sending STUN binding error: reason=" << reason
+                       << " to " << addr.ToString();
+}
+
+void Port::OnMessage(talk_base::Message *pmsg) {
+  ASSERT(pmsg->message_id == MSG_CHECKTIMEOUT);
+  ASSERT(lifetime_ == LT_PRETIMEOUT);
+  lifetime_ = LT_POSTTIMEOUT;
+  CheckTimeout();
+}
+
+std::string Port::ToString() const {
+  std::stringstream ss;
+  ss << "Port[" << name_ << ":" << type_ << ":" << network_->ToString() << "]";
+  return ss.str();
+}
+
+void Port::EnablePortPackets() {
+  enable_port_packets_ = true;
+}
+
+void Port::Start() {
+  // The port sticks around for a minimum lifetime, after which
+  // we destroy it when it drops to zero connections.
+  if (lifetime_ == LT_PRESTART) {
+    lifetime_ = LT_PRETIMEOUT;
+    thread_->PostDelayed(kPortTimeoutDelay, this, MSG_CHECKTIMEOUT);
+  } else {
+    LOG_J(LS_WARNING, this) << "Port restart attempted";
+  }
+}
+
+void Port::OnConnectionDestroyed(Connection* conn) {
+  AddressMap::iterator iter =
+      connections_.find(conn->remote_candidate().address());
+  ASSERT(iter != connections_.end());
+  connections_.erase(iter);
+
+  CheckTimeout();
+}
+
+void Port::Destroy() {
+  ASSERT(connections_.empty());
+  LOG_J(LS_INFO, this) << "Port deleted";
+  SignalDestroyed(this);
+  delete this;
+}
+
+void Port::CheckTimeout() {
+  // If this port has no connections, then there's no reason to keep it around.
+  // When the connections time out (both read and write), they will delete
+  // themselves, so if we have any connections, they are either readable or
+  // writable (or still connecting).
+  if ((lifetime_ == LT_POSTTIMEOUT) && connections_.empty()) {
+    Destroy();
+  }
+}
+
+// A ConnectionRequest is a simple STUN ping used to determine writability.
+class ConnectionRequest : public StunRequest {
+ public:
+  explicit ConnectionRequest(Connection* connection) : connection_(connection) {
+  }
+
+  virtual ~ConnectionRequest() {
+  }
+
+  virtual void Prepare(StunMessage* request) {
+    request->SetType(STUN_BINDING_REQUEST);
+    StunByteStringAttribute* username_attr =
+        StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
+    std::string username = connection_->remote_candidate().username();
+    username.append(connection_->port()->username_fragment());
+    username_attr->CopyBytes(username.c_str(), username.size());
+    request->AddAttribute(username_attr);
+  }
+
+  virtual void OnResponse(StunMessage* response) {
+    connection_->OnConnectionRequestResponse(this, response);
+  }
+
+  virtual void OnErrorResponse(StunMessage* response) {
+    connection_->OnConnectionRequestErrorResponse(this, response);
+  }
+
+  virtual void OnTimeout() {
+    connection_->OnConnectionRequestTimeout(this);
+  }
+
+  virtual int GetNextDelay() {
+    // Each request is sent only once.  After a single delay , the request will
+    // time out.
+    timeout_ = true;
+    return CONNECTION_RESPONSE_TIMEOUT;
+  }
+
+ private:
+  Connection* connection_;
+};
+
+//
+// Connection
+//
+
+Connection::Connection(Port* port, size_t index,
+                       const Candidate& remote_candidate)
+  : port_(port), local_candidate_index_(index),
+    remote_candidate_(remote_candidate), read_state_(STATE_READ_TIMEOUT),
+    write_state_(STATE_WRITE_CONNECT), connected_(true), pruned_(false),
+    requests_(port->thread()), rtt_(DEFAULT_RTT),
+    last_ping_sent_(0), last_ping_received_(0), last_data_received_(0),
+    reported_(false) {
+  // Wire up to send stun packets
+  requests_.SignalSendPacket.connect(this, &Connection::OnSendStunPacket);
+  LOG_J(LS_INFO, this) << "Connection created";
+}
+
+Connection::~Connection() {
+}
+
+const Candidate& Connection::local_candidate() const {
+  if (local_candidate_index_ < port_->candidates().size())
+    return port_->candidates()[local_candidate_index_];
+  ASSERT(false);
+  static Candidate foo;
+  return foo;
+}
+
+void Connection::set_read_state(ReadState value) {
+  ReadState old_value = read_state_;
+  read_state_ = value;
+  if (value != old_value) {
+    LOG_J(LS_VERBOSE, this) << "set_read_state";
+    SignalStateChange(this);
+    CheckTimeout();
+  }
+}
+
+void Connection::set_write_state(WriteState value) {
+  WriteState old_value = write_state_;
+  write_state_ = value;
+  if (value != old_value) {
+    LOG_J(LS_VERBOSE, this) << "set_write_state";
+    SignalStateChange(this);
+    CheckTimeout();
+  }
+}
+
+void Connection::set_connected(bool value) {
+  bool old_value = connected_;
+  connected_ = value;
+  if (value != old_value) {
+    LOG_J(LS_VERBOSE, this) << "set_connected";
+  }
+}
+
+void Connection::OnSendStunPacket(const void* data, size_t size,
+                                  StunRequest* req) {
+  if (port_->SendTo(data, size, remote_candidate_.address(), false) < 0) {
+    LOG_J(LS_WARNING, this) << "Failed to send STUN ping " << req->id();
+  }
+}
+
+void Connection::OnReadPacket(const char* data, size_t size) {
+  StunMessage* msg;
+  std::string remote_username;
+  const talk_base::SocketAddress& addr(remote_candidate_.address());
+  if (!port_->GetStunMessage(data, size, addr, &msg, &remote_username)) {
+    // The packet did not parse as a valid STUN message
+
+    // If this connection is readable, then pass along the packet.
+    if (read_state_ == STATE_READABLE) {
+      // readable means data from this address is acceptable
+      // Send it on!
+
+      last_data_received_ = talk_base::Time();
+      recv_rate_tracker_.Update(size);
+      SignalReadPacket(this, data, size);
+
+      // If timed out sending writability checks, start up again
+      if (!pruned_ && (write_state_ == STATE_WRITE_TIMEOUT))
+        set_write_state(STATE_WRITE_CONNECT);
+    } else {
+      // Not readable means the remote address hasn't sent a valid
+      // binding request yet.
+
+      LOG_J(LS_WARNING, this)
+        << "Received non-STUN packet from an unreadable connection.";
+    }
+  } else if (!msg) {
+    // The packet was STUN, but was already handled internally.
+  } else if (remote_username != remote_candidate_.username()) {
+    // The packet had the right local username, but the remote username was
+    // not the right one for the remote address.
+    if (msg->type() == STUN_BINDING_REQUEST) {
+      LOG_J(LS_ERROR, this) << "Received STUN request with bad remote username "
+                            << remote_username;
+      port_->SendBindingErrorResponse(msg, addr, STUN_ERROR_BAD_REQUEST,
+                                      STUN_ERROR_REASON_BAD_REQUEST);
+    } else if (msg->type() == STUN_BINDING_RESPONSE ||
+               msg->type() == STUN_BINDING_ERROR_RESPONSE) {
+      LOG_J(LS_ERROR, this) << "Received STUN response with bad remote username"
+                            " " << remote_username;
+    }
+    delete msg;
+  } else {
+    // The packet is STUN, with the right username.
+    // If this is a STUN request, then update the readable bit and respond.
+    // If this is a STUN response, then update the writable bit.
+
+    switch (msg->type()) {
+    case STUN_BINDING_REQUEST:
+      // Incoming, validated stun request from remote peer.
+      // This call will also set the connection readable.
+
+      port_->SendBindingResponse(msg, addr);
+
+      // If timed out sending writability checks, start up again
+      if (!pruned_ && (write_state_ == STATE_WRITE_TIMEOUT))
+        set_write_state(STATE_WRITE_CONNECT);
+      break;
+
+    case STUN_BINDING_RESPONSE:
+    case STUN_BINDING_ERROR_RESPONSE:
+      // Response from remote peer. Does it match request sent?
+      // This doesn't just check, it makes callbacks if transaction
+      // id's match
+      requests_.CheckResponse(msg);
+      break;
+
+    default:
+      ASSERT(false);
+      break;
+    }
+
+    // Done with the message; delete
+
+    delete msg;
+  }
+}
+
+void Connection::Prune() {
+  if (!pruned_) {
+    LOG_J(LS_VERBOSE, this) << "Connection pruned";
+    pruned_ = true;
+    requests_.Clear();
+    set_write_state(STATE_WRITE_TIMEOUT);
+  }
+}
+
+void Connection::Destroy() {
+  LOG_J(LS_VERBOSE, this) << "Connection destroyed";
+  set_read_state(STATE_READ_TIMEOUT);
+  set_write_state(STATE_WRITE_TIMEOUT);
+}
+
+void Connection::UpdateState(uint32 now) {
+  uint32 rtt = ConservativeRTTEstimate(rtt_);
+
+  std::string pings;
+  for (size_t i = 0; i < pings_since_last_response_.size(); ++i) {
+    char buf[32];
+    talk_base::sprintfn(buf, sizeof(buf), "%u",
+        pings_since_last_response_[i]);
+    pings.append(buf).append(" ");
+  }
+  LOG_J(LS_VERBOSE, this) << "UpdateState(): pings_since_last_response_=" <<
+      pings << ", rtt=" << rtt << ", now=" << now;
+
+  // Check the readable state.
+  //
+  // Since we don't know how many pings the other side has attempted, the best
+  // test we can do is a simple window.
+
+  if ((read_state_ == STATE_READABLE) &&
+      (last_ping_received_ + CONNECTION_READ_TIMEOUT <= now)) {
+    LOG_J(LS_INFO, this) << "Unreadable after "
+                         << now - last_ping_received_
+                         << " ms without a ping, rtt=" << rtt;
+    set_read_state(STATE_READ_TIMEOUT);
+  }
+
+  // Check the writable state.  (The order of these checks is important.)
+  //
+  // Before becoming unwritable, we allow for a fixed number of pings to fail
+  // (i.e., receive no response).  We also have to give the response time to
+  // get back, so we include a conservative estimate of this.
+  //
+  // Before timing out writability, we give a fixed amount of time.  This is to
+  // allow for changes in network conditions.
+
+  if ((write_state_ == STATE_WRITABLE) &&
+      TooManyFailures(pings_since_last_response_,
+                      CONNECTION_WRITE_CONNECT_FAILURES,
+                      rtt,
+                      now) &&
+      TooLongWithoutResponse(pings_since_last_response_,
+                             CONNECTION_WRITE_CONNECT_TIMEOUT,
+                             now)) {
+    uint32 max_pings = CONNECTION_WRITE_CONNECT_FAILURES;
+    LOG_J(LS_INFO, this) << "Unwritable after " << max_pings
+                         << " ping failures and "
+                         << now - pings_since_last_response_[0]
+                         << " ms without a response,"
+                         << " ms since last received ping="
+                         << now - last_ping_received_
+                         << " ms since last received data="
+                         << now - last_data_received_
+                         << " rtt=" << rtt;
+    set_write_state(STATE_WRITE_CONNECT);
+  }
+
+  if ((write_state_ == STATE_WRITE_CONNECT) &&
+      TooLongWithoutResponse(pings_since_last_response_,
+                             CONNECTION_WRITE_TIMEOUT,
+                             now)) {
+    LOG_J(LS_INFO, this) << "Timed out after "
+                         << now - pings_since_last_response_[0]
+                         << " ms without a response, rtt=" << rtt;
+    set_write_state(STATE_WRITE_TIMEOUT);
+  }
+}
+
+void Connection::Ping(uint32 now) {
+  ASSERT(connected_);
+  last_ping_sent_ = now;
+  pings_since_last_response_.push_back(now);
+  ConnectionRequest *req = new ConnectionRequest(this);
+  LOG_J(LS_VERBOSE, this) << "Sending STUN ping " << req->id() << " at " << now;
+  requests_.Send(req);
+}
+
+void Connection::ReceivedPing() {
+  last_ping_received_ = talk_base::Time();
+  set_read_state(STATE_READABLE);
+}
+
+std::string Connection::ToString() const {
+  const char CONNECT_STATE_ABBREV[2] = {
+    '-',  // not connected (false)
+    'C',  // connected (true)
+  };
+  const char READ_STATE_ABBREV[2] = {
+    'R',  // STATE_READABLE
+    '-',  // STATE_READ_TIMEOUT
+  };
+  const char WRITE_STATE_ABBREV[3] = {
+    'W',  // STATE_WRITABLE
+    'w',  // STATE_WRITE_CONNECT
+    '-',  // STATE_WRITE_TIMEOUT
+  };
+  const Candidate& local = local_candidate();
+  const Candidate& remote = remote_candidate();
+  std::stringstream ss;
+  ss << "Conn[" << local.generation()
+     << ":" << local.name() << ":" << local.type() << ":"
+     << local.protocol() << ":" << local.address().ToString()
+     << "->" << remote.name() << ":" << remote.type() << ":"
+     << remote.protocol() << ":" << remote.address().ToString()
+     << "|"
+     << CONNECT_STATE_ABBREV[connected()]
+     << READ_STATE_ABBREV[read_state()]
+     << WRITE_STATE_ABBREV[write_state()]
+     << "|";
+  if (rtt_ < DEFAULT_RTT) {
+    ss << rtt_ << "]";
+  } else {
+    ss << "-]";
+  }
+  return ss.str();
+}
+
+void Connection::OnConnectionRequestResponse(ConnectionRequest* request,
+                                             StunMessage* response) {
+  // We've already validated that this is a STUN binding response with
+  // the correct local and remote username for this connection.
+  // So if we're not already, become writable. We may be bringing a pruned
+  // connection back to life, but if we don't really want it, we can always
+  // prune it again.
+  uint32 rtt = request->Elapsed();
+  set_write_state(STATE_WRITABLE);
+
+  std::string pings;
+  for (size_t i = 0; i < pings_since_last_response_.size(); ++i) {
+    char buf[32];
+    talk_base::sprintfn(buf, sizeof(buf), "%u",
+        pings_since_last_response_[i]);
+    pings.append(buf).append(" ");
+  }
+
+  LOG_J(LS_VERBOSE, this) << "Received STUN ping response " << request->id()
+                          << ", pings_since_last_response_=" << pings
+                          << ", rtt=" << rtt;
+
+  pings_since_last_response_.clear();
+  rtt_ = (RTT_RATIO * rtt_ + rtt) / (RTT_RATIO + 1);
+}
+
+void Connection::OnConnectionRequestErrorResponse(ConnectionRequest* request,
+                                                  StunMessage* response) {
+  const StunErrorCodeAttribute* error = response->GetErrorCode();
+  uint32 error_code = error ?
+      error->error_code() : static_cast<uint32>(STUN_ERROR_GLOBAL_FAILURE);
+
+  if ((error_code == STUN_ERROR_UNKNOWN_ATTRIBUTE)
+      || (error_code == STUN_ERROR_SERVER_ERROR)
+      || (error_code == STUN_ERROR_UNAUTHORIZED)) {
+    // Recoverable error, retry
+  } else if (error_code == STUN_ERROR_STALE_CREDENTIALS) {
+    // Race failure, retry
+  } else {
+    // This is not a valid connection.
+    LOG_J(LS_ERROR, this) << "Received STUN error response, code="
+                          << error_code << "; killing connection";
+    set_write_state(STATE_WRITE_TIMEOUT);
+  }
+}
+
+void Connection::OnConnectionRequestTimeout(ConnectionRequest* request) {
+  // Log at LS_INFO if we miss a ping on a writable connection.
+  talk_base::LoggingSeverity sev = (write_state_ == STATE_WRITABLE) ?
+      talk_base::LS_INFO : talk_base::LS_VERBOSE;
+  uint32 when = talk_base::Time() - request->Elapsed();
+  size_t failures;
+  for (failures = 0; failures < pings_since_last_response_.size(); ++failures) {
+    if (pings_since_last_response_[failures] > when) {
+      break;
+    }
+  }
+  ASSERT(failures > 0);
+  LOG_JV(sev, this) << "Timing-out STUN ping " << request->id()
+                    << " after " << request->Elapsed()
+                    << " ms, failures=" << failures;
+}
+
+void Connection::CheckTimeout() {
+  // If both read and write have timed out, then this connection can contribute
+  // no more to p2p socket unless at some later date readability were to come
+  // back.  However, we gave readability a long time to timeout, so at this
+  // point, it seems fair to get rid of this connection.
+  if ((read_state_ == STATE_READ_TIMEOUT) &&
+      (write_state_ == STATE_WRITE_TIMEOUT)) {
+    port_->thread()->Post(this, MSG_DELETE);
+  }
+}
+
+void Connection::OnMessage(talk_base::Message *pmsg) {
+  ASSERT(pmsg->message_id == MSG_DELETE);
+
+  LOG_J(LS_INFO, this) << "Connection deleted";
+  SignalDestroyed(this);
+  delete this;
+}
+
+size_t Connection::recv_bytes_second() {
+  return recv_rate_tracker_.units_second();
+}
+
+size_t Connection::recv_total_bytes() {
+  return recv_rate_tracker_.total_units();
+}
+
+size_t Connection::sent_bytes_second() {
+  return send_rate_tracker_.units_second();
+}
+
+size_t Connection::sent_total_bytes() {
+  return send_rate_tracker_.total_units();
+}
+
+ProxyConnection::ProxyConnection(Port* port, size_t index,
+                                 const Candidate& candidate)
+  : Connection(port, index, candidate), error_(0) {
+}
+
+int ProxyConnection::Send(const void* data, size_t size) {
+  if (write_state() != STATE_WRITABLE) {
+    error_ = EWOULDBLOCK;
+    return SOCKET_ERROR;
+  }
+  int sent = port_->SendTo(data, size, remote_candidate_.address(), true);
+  if (sent <= 0) {
+    ASSERT(sent < 0);
+    error_ = port_->GetError();
+  } else {
+    send_rate_tracker_.Update(sent);
+  }
+  return sent;
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/port.h b/talk/p2p/base/port.h
new file mode 100644
index 0000000..8304146
--- /dev/null
+++ b/talk/p2p/base/port.h
@@ -0,0 +1,424 @@
+/*
+ * 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_P2P_BASE_PORT_H_
+#define TALK_P2P_BASE_PORT_H_
+
+#include <string>
+#include <vector>
+#include <map>
+
+#include "talk/base/network.h"
+#include "talk/base/packetsocketfactory.h"
+#include "talk/base/proxyinfo.h"
+#include "talk/base/ratetracker.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/candidate.h"
+#include "talk/p2p/base/stun.h"
+#include "talk/p2p/base/stunrequest.h"
+
+namespace talk_base {
+class AsyncPacketSocket;
+}
+
+namespace cricket {
+
+class Connection;
+class ConnectionRequest;
+
+enum ProtocolType {
+  PROTO_UDP,
+  PROTO_TCP,
+  PROTO_SSLTCP,
+  PROTO_LAST = PROTO_SSLTCP
+};
+
+const char* ProtoToString(ProtocolType proto);
+bool StringToProto(const char* value, ProtocolType* proto);
+
+struct ProtocolAddress {
+  talk_base::SocketAddress address;
+  ProtocolType proto;
+
+  ProtocolAddress(const talk_base::SocketAddress& a, ProtocolType p)
+    : address(a), proto(p) { }
+};
+
+// Represents a local communication mechanism that can be used to create
+// connections to similar mechanisms of the other client.  Subclasses of this
+// one add support for specific mechanisms like local UDP ports.
+class Port : public talk_base::MessageHandler, public sigslot::has_slots<> {
+ public:
+  Port(talk_base::Thread* thread, const std::string& type,
+       talk_base::PacketSocketFactory* factory, talk_base::Network* network,
+       uint32 ip, int min_port, int max_port);
+  virtual ~Port();
+
+  // The thread on which this port performs its I/O.
+  talk_base::Thread* thread() { return thread_; }
+
+  // The factory used to create the sockets of this port.
+  talk_base::PacketSocketFactory* socket_factory() const { return factory_; }
+  void set_socket_factory(talk_base::PacketSocketFactory* factory) {
+    factory_ = factory;
+  }
+
+  // Each port is identified by a name (for debugging purposes).
+  const std::string& name() const { return name_; }
+  void set_name(const std::string& name) { name_ = name; }
+
+  // In order to establish a connection to this Port (so that real data can be
+  // sent through), the other side must send us a STUN binding request that is
+  // authenticated with this username and password.
+  // Fills in the username fragment and password.  These will be initially set
+  // in the constructor to random values.  Subclasses or tests can override.
+  // TODO: Change this to "username" rather than "username_fragment".
+  const std::string& username_fragment() const { return username_frag_; }
+  void set_username_fragment(const std::string& username) {
+    username_frag_ = username;
+  }
+
+  const std::string& password() const { return password_; }
+  void set_password(const std::string& password) { password_ = password; }
+
+
+  // A value in [0,1] that indicates the preference for this port versus other
+  // ports on this client.  (Larger indicates more preference.)
+  float preference() const { return preference_; }
+  void set_preference(float preference) { preference_ = preference; }
+
+  // Identifies the port type.
+  const std::string& type() const { return type_; }
+
+  // Identifies network that this port was allocated on.
+  talk_base::Network* network() { return network_; }
+
+  // Identifies the generation that this port was created in.
+  uint32 generation() { return generation_; }
+  void set_generation(uint32 generation) { generation_ = generation; }
+
+  // PrepareAddress will attempt to get an address for this port that other
+  // clients can send to.  It may take some time before the address is read.
+  // Once it is ready, we will send SignalAddressReady.  If errors are
+  // preventing the port from getting an address, it may send
+  // SignalAddressError.
+  virtual void PrepareAddress() = 0;
+  sigslot::signal1<Port*> SignalAddressReady;
+  sigslot::signal1<Port*> SignalAddressError;
+
+  // Provides all of the above information in one handy object.
+  const std::vector<Candidate>& candidates() const { return candidates_; }
+
+  // Returns a map containing all of the connections of this port, keyed by the
+  // remote address.
+  typedef std::map<talk_base::SocketAddress, Connection*> AddressMap;
+  const AddressMap& connections() { return connections_; }
+
+  // Returns the connection to the given address or NULL if none exists.
+  Connection* GetConnection(const talk_base::SocketAddress& remote_addr);
+
+  // Creates a new connection to the given address.
+  enum CandidateOrigin { ORIGIN_THIS_PORT, ORIGIN_OTHER_PORT, ORIGIN_MESSAGE };
+  virtual Connection* CreateConnection(const Candidate& remote_candidate,
+    CandidateOrigin origin) = 0;
+
+  // Called each time a connection is created.
+  sigslot::signal2<Port*, Connection*> SignalConnectionCreated;
+
+  // Sends the given packet to the given address, provided that the address is
+  // that of a connection or an address that has sent to us already.
+  virtual int SendTo(
+      const void* data, size_t size, const talk_base::SocketAddress& addr,
+      bool payload) = 0;
+
+  // Indicates that we received a successful STUN binding request from an
+  // address that doesn't correspond to any current connection.  To turn this
+  // into a real connection, call CreateConnection.
+  sigslot::signal4<Port*, const talk_base::SocketAddress&, StunMessage*,
+                   const std::string&> SignalUnknownAddress;
+
+  // Sends a response message (normal or error) to the given request.  One of
+  // these methods should be called as a response to SignalUnknownAddress.
+  // NOTE: You MUST call CreateConnection BEFORE SendBindingResponse.
+  void SendBindingResponse(StunMessage* request,
+                           const talk_base::SocketAddress& addr);
+  void SendBindingErrorResponse(
+      StunMessage* request, const talk_base::SocketAddress& addr,
+      int error_code, const std::string& reason);
+
+  // Indicates that errors occurred when performing I/O.
+  sigslot::signal2<Port*, int> SignalReadError;
+  sigslot::signal2<Port*, int> SignalWriteError;
+
+  // Functions on the underlying socket(s).
+  virtual int SetOption(talk_base::Socket::Option opt, int value) = 0;
+  virtual int GetError() = 0;
+
+  void set_proxy(const std::string& user_agent,
+                 const talk_base::ProxyInfo& proxy) {
+    user_agent_ = user_agent;
+    proxy_ = proxy;
+  }
+  const std::string& user_agent() { return user_agent_; }
+  const talk_base::ProxyInfo& proxy() { return proxy_; }
+
+  // Normally, packets arrive through a connection (or they result signaling of
+  // unknown address).  Calling this method turns off delivery of packets
+  // through their respective connection and instead delivers every packet
+  // through this port.
+  void EnablePortPackets();
+  sigslot::signal4<Port*, const char*, size_t, const talk_base::SocketAddress&>
+      SignalReadPacket;
+
+  // Indicates to the port that its official use has now begun.  This will
+  // start the timer that checks to see if the port is being used.
+  void Start();
+
+  // Called if the port has no connections and is no longer useful.
+  void Destroy();
+
+  // Signaled when this port decides to delete itself because it no longer has
+  // any usefulness.
+  sigslot::signal1<Port*> SignalDestroyed;
+
+  virtual void OnMessage(talk_base::Message *pmsg);
+
+  // Debugging description of this port
+  std::string ToString() const;
+
+ protected:
+  // Fills in the local address of the port.
+  void AddAddress(const talk_base::SocketAddress& address,
+                  const std::string& protocol, bool final);
+
+  // Adds the given connection to the list.  (Deleting removes them.)
+  void AddConnection(Connection* conn);
+
+  // Called when a packet is received from an unknown address that is not
+  // currently a connection.  If this is an authenticated STUN binding request,
+  // then we will signal the client.
+  void OnReadPacket(const char* data, size_t size,
+                    const talk_base::SocketAddress& addr);
+
+
+  // If the given data comprises a complete and correct STUN message then the
+  // return value is true, otherwise false. If the message username corresponds
+  // with this port's username fragment, msg will contain the parsed STUN
+  // message.  Otherwise, the function may send a STUN response internally.
+  // remote_username contains the remote fragment of the STUN username.
+  bool GetStunMessage(const char* data, size_t size,
+                      const talk_base::SocketAddress& addr,
+                      StunMessage** out_msg, std::string* out_username);
+
+  // TODO: make these members private
+  talk_base::Thread* thread_;
+  talk_base::PacketSocketFactory* factory_;
+  std::string type_;
+  talk_base::Network* network_;
+  uint32 ip_;
+  int min_port_;
+  int max_port_;
+  uint32 generation_;
+  std::string name_;
+  std::string username_frag_;
+  std::string password_;
+  float preference_;
+  std::vector<Candidate> candidates_;
+  AddressMap connections_;
+  enum Lifetime { LT_PRESTART, LT_PRETIMEOUT, LT_POSTTIMEOUT } lifetime_;
+  bool enable_port_packets_;
+
+ private:
+  // Called when one of our connections deletes itself.
+  void OnConnectionDestroyed(Connection* conn);
+
+  // Checks if this port is useless, and hence, should be destroyed.
+  void CheckTimeout();
+
+  // Information to use when going through a proxy.
+  std::string user_agent_;
+  talk_base::ProxyInfo proxy_;
+
+  friend class Connection;
+};
+
+// Represents a communication link between a port on the local client and a
+// port on the remote client.
+class Connection : public talk_base::MessageHandler,
+    public sigslot::has_slots<> {
+ public:
+  virtual ~Connection();
+
+  // The local port where this connection sends and receives packets.
+  Port* port() { return port_; }
+  const Port* port() const { return port_; }
+
+  // Returns the description of the local port
+  virtual const Candidate& local_candidate() const;
+
+  // Returns the description of the remote port to which we communicate.
+  const Candidate& remote_candidate() const { return remote_candidate_; }
+
+  enum ReadState {
+    STATE_READABLE     = 0,  // we have received pings recently
+    STATE_READ_TIMEOUT = 1   // we haven't received pings in a while
+  };
+
+  ReadState read_state() const { return read_state_; }
+
+  enum WriteState {
+    STATE_WRITABLE      = 0,  // we have received ping responses recently
+    STATE_WRITE_CONNECT = 1,  // we have had a few ping failures
+    STATE_WRITE_TIMEOUT = 2   // we have had a large number of ping failures
+  };
+
+  WriteState write_state() const { return write_state_; }
+
+  // Determines whether the connection has finished connecting.  This can only
+  // be false for TCP connections.
+  bool connected() const { return connected_; }
+
+  // Estimate of the round-trip time over this connection.
+  uint32 rtt() const { return rtt_; }
+
+  size_t sent_total_bytes();
+  size_t sent_bytes_second();
+  size_t recv_total_bytes();
+  size_t recv_bytes_second();
+  sigslot::signal1<Connection*> SignalStateChange;
+
+  // Sent when the connection has decided that it is no longer of value.  It
+  // will delete itself immediately after this call.
+  sigslot::signal1<Connection*> SignalDestroyed;
+
+  // The connection can send and receive packets asynchronously.  This matches
+  // the interface of AsyncPacketSocket, which may use UDP or TCP under the
+  // covers.
+  virtual int Send(const void* data, size_t size) = 0;
+
+  // Error if Send() returns < 0
+  virtual int GetError() = 0;
+
+  sigslot::signal3<Connection*, const char*, size_t> SignalReadPacket;
+
+  // Called when a packet is received on this connection.
+  void OnReadPacket(const char* data, size_t size);
+
+  // Called when a connection is determined to be no longer useful to us.  We
+  // still keep it around in case the other side wants to use it.  But we can
+  // safely stop pinging on it and we can allow it to time out if the other
+  // side stops using it as well.
+  bool pruned() const { return pruned_; }
+  void Prune();
+
+  // Makes the connection go away.
+  void Destroy();
+
+  // Checks that the state of this connection is up-to-date.  The argument is
+  // the current time, which is compared against various timeouts.
+  void UpdateState(uint32 now);
+
+  // Called when this connection should try checking writability again.
+  uint32 last_ping_sent() const { return last_ping_sent_; }
+  void Ping(uint32 now);
+
+  // Called whenever a valid ping is received on this connection.  This is
+  // public because the connection intercepts the first ping for us.
+  void ReceivedPing();
+
+  // Debugging description of this connection
+  std::string ToString() const;
+
+  bool reported() const { return reported_; }
+  void set_reported(bool reported) { reported_ = reported;}
+
+ protected:
+  // Constructs a new connection to the given remote port.
+  Connection(Port* port, size_t index, const Candidate& candidate);
+
+  // Called back when StunRequestManager has a stun packet to send
+  void OnSendStunPacket(const void* data, size_t size, StunRequest* req);
+
+  // Callbacks from ConnectionRequest
+  void OnConnectionRequestResponse(ConnectionRequest* req,
+                                   StunMessage* response);
+  void OnConnectionRequestErrorResponse(ConnectionRequest* req,
+                                        StunMessage* response);
+  void OnConnectionRequestTimeout(ConnectionRequest* req);
+
+  // Changes the state and signals if necessary.
+  void set_read_state(ReadState value);
+  void set_write_state(WriteState value);
+  void set_connected(bool value);
+
+  // Checks if this connection is useless, and hence, should be destroyed.
+  void CheckTimeout();
+
+  void OnMessage(talk_base::Message *pmsg);
+
+  Port* port_;
+  size_t local_candidate_index_;
+  Candidate remote_candidate_;
+  ReadState read_state_;
+  WriteState write_state_;
+  bool connected_;
+  bool pruned_;
+  StunRequestManager requests_;
+  uint32 rtt_;
+  uint32 last_ping_sent_;      // last time we sent a ping to the other side
+  uint32 last_ping_received_;  // last time we received a ping from the other
+                               // side
+  uint32 last_data_received_;
+  std::vector<uint32> pings_since_last_response_;
+
+  talk_base::RateTracker recv_rate_tracker_;
+  talk_base::RateTracker send_rate_tracker_;
+
+ private:
+  bool reported_;
+
+  friend class Port;
+  friend class ConnectionRequest;
+};
+
+// ProxyConnection defers all the interesting work to the port
+class ProxyConnection : public Connection {
+ public:
+  ProxyConnection(Port* port, size_t index, const Candidate& candidate);
+
+  virtual int Send(const void* data, size_t size);
+  virtual int GetError() { return error_; }
+
+ private:
+  int error_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_PORT_H_
diff --git a/talk/p2p/base/portallocator.h b/talk/p2p/base/portallocator.h
new file mode 100644
index 0000000..175bdbc
--- /dev/null
+++ b/talk/p2p/base/portallocator.h
@@ -0,0 +1,128 @@
+/*
+ * 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_P2P_BASE_PORTALLOCATOR_H_
+#define TALK_P2P_BASE_PORTALLOCATOR_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/sigslot.h"
+#include "talk/p2p/base/port.h"
+
+namespace cricket {
+
+// PortAllocator is responsible for allocating Port types for a given
+// P2PSocket. It also handles port freeing.
+//
+// Clients can override this class to control port allocation, including
+// what kinds of ports are allocated.
+
+const uint32 PORTALLOCATOR_DISABLE_UDP = 0x01;
+const uint32 PORTALLOCATOR_DISABLE_STUN = 0x02;
+const uint32 PORTALLOCATOR_DISABLE_RELAY = 0x04;
+const uint32 PORTALLOCATOR_DISABLE_TCP = 0x08;
+const uint32 PORTALLOCATOR_ENABLE_SHAKER = 0x10;
+
+const uint32 kDefaultPortAllocatorFlags = 0;
+
+class PortAllocatorSession : public sigslot::has_slots<> {
+ public:
+  explicit PortAllocatorSession(uint32 flags) : flags_(flags) {}
+
+  // Subclasses should clean up any ports created.
+  virtual ~PortAllocatorSession() {}
+
+  uint32 flags() const { return flags_; }
+  void set_flags(uint32 flags) { flags_ = flags; }
+
+  // Prepares an initial set of ports to try.
+  virtual void GetInitialPorts() = 0;
+
+  // Starts and stops the flow of additional ports to try.
+  virtual void StartGetAllPorts() = 0;
+  virtual void StopGetAllPorts() = 0;
+  virtual bool IsGettingAllPorts() = 0;
+
+  sigslot::signal2<PortAllocatorSession*, Port*> SignalPortReady;
+  sigslot::signal2<PortAllocatorSession*,
+                   const std::vector<Candidate>&> SignalCandidatesReady;
+
+  uint32 generation() { return generation_; }
+  void set_generation(uint32 generation) { generation_ = generation; }
+
+ private:
+  uint32 flags_;
+  uint32 generation_;
+};
+
+class PortAllocator {
+ public:
+  PortAllocator() :
+      flags_(kDefaultPortAllocatorFlags),
+      min_port_(0),
+      max_port_(0) {
+  }
+  virtual ~PortAllocator() {}
+
+  virtual PortAllocatorSession *CreateSession(const std::string &name,
+      const std::string &session_type) = 0;
+
+  uint32 flags() const { return flags_; }
+  void set_flags(uint32 flags) { flags_ = flags; }
+
+  const std::string& user_agent() const { return agent_; }
+  const talk_base::ProxyInfo& proxy() const { return proxy_; }
+  void set_proxy(const std::string& agent, const talk_base::ProxyInfo& proxy) {
+    agent_ = agent;
+    proxy_ = proxy;
+  }
+
+  // Gets/Sets the port range to use when choosing client ports.
+  int min_port() const { return min_port_; }
+  int max_port() const { return max_port_; }
+  bool SetPortRange(int min_port, int max_port) {
+    if (min_port > max_port) {
+      return false;
+    }
+
+    min_port_ = min_port;
+    max_port_ = max_port;
+    return true;
+  }
+
+ protected:
+  uint32 flags_;
+  std::string agent_;
+  talk_base::ProxyInfo proxy_;
+  int min_port_;
+  int max_port_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_PORTALLOCATOR_H_
diff --git a/talk/p2p/base/pseudotcp.cc b/talk/p2p/base/pseudotcp.cc
new file mode 100644
index 0000000..c342dbc
--- /dev/null
+++ b/talk/p2p/base/pseudotcp.cc
@@ -0,0 +1,1055 @@
+/*
+ * 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/p2p/base/pseudotcp.h"
+
+#include <cstdio>
+#include <cstdlib>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/byteorder.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/socket.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/time.h"
+
+// The following logging is for detailed (packet-level) analysis only.
+#define _DBG_NONE     0
+#define _DBG_NORMAL   1
+#define _DBG_VERBOSE  2
+#define _DEBUGMSG _DBG_NONE
+
+namespace cricket {
+
+//////////////////////////////////////////////////////////////////////
+// Network Constants
+//////////////////////////////////////////////////////////////////////
+
+// Standard MTUs
+const uint16 PACKET_MAXIMUMS[] = {
+  65535,    // Theoretical maximum, Hyperchannel
+  32000,    // Nothing
+  17914,    // 16Mb IBM Token Ring
+  8166,   // IEEE 802.4
+  //4464,   // IEEE 802.5 (4Mb max)
+  4352,   // FDDI
+  //2048,   // Wideband Network
+  2002,   // IEEE 802.5 (4Mb recommended)
+  //1536,   // Expermental Ethernet Networks
+  //1500,   // Ethernet, Point-to-Point (default)
+  1492,   // IEEE 802.3
+  1006,   // SLIP, ARPANET
+  //576,    // X.25 Networks
+  //544,    // DEC IP Portal
+  //512,    // NETBIOS
+  508,    // IEEE 802/Source-Rt Bridge, ARCNET
+  296,    // Point-to-Point (low delay)
+  //68,     // Official minimum
+  0,      // End of list marker
+};
+
+const uint32 MAX_PACKET = 65535;
+// Note: we removed lowest level because packet overhead was larger!
+const uint32 MIN_PACKET = 296;
+
+const uint32 IP_HEADER_SIZE = 20; // (+ up to 40 bytes of options?)
+const uint32 ICMP_HEADER_SIZE = 8;
+const uint32 UDP_HEADER_SIZE = 8;
+// TODO: Make JINGLE_HEADER_SIZE transparent to this code?
+const uint32 JINGLE_HEADER_SIZE = 64; // when relay framing is in use
+
+//////////////////////////////////////////////////////////////////////
+// Global Constants and Functions
+//////////////////////////////////////////////////////////////////////
+//
+//    0                   1                   2                   3
+//    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+//    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//  0 |                      Conversation Number                      |
+//    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//  4 |                        Sequence Number                        |
+//    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//  8 |                     Acknowledgment Number                     |
+//    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//    |               |   |U|A|P|R|S|F|                               |
+// 12 |    Control    |   |R|C|S|S|Y|I|            Window             |
+//    |               |   |G|K|H|T|N|N|                               |
+//    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// 16 |                       Timestamp sending                       |
+//    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// 20 |                      Timestamp receiving                      |
+//    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// 24 |                             data                              |
+//    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//
+//////////////////////////////////////////////////////////////////////
+
+#define PSEUDO_KEEPALIVE 0
+
+const uint32 MAX_SEQ = 0xFFFFFFFF;
+const uint32 HEADER_SIZE = 24;
+const uint32 PACKET_OVERHEAD = HEADER_SIZE + UDP_HEADER_SIZE + IP_HEADER_SIZE + JINGLE_HEADER_SIZE;
+
+const uint32 MIN_RTO   =   250; // 250 ms (RFC1122, Sec 4.2.3.1 "fractions of a second")
+const uint32 DEF_RTO   =  3000; // 3 seconds (RFC1122, Sec 4.2.3.1)
+const uint32 MAX_RTO   = 60000; // 60 seconds
+const uint32 ACK_DELAY =   100; // 100 milliseconds
+
+const uint8 FLAG_CTL = 0x02;
+const uint8 FLAG_RST = 0x04;
+
+const uint8 CTL_CONNECT = 0;
+//const uint8 CTL_REDIRECT = 1;
+const uint8 CTL_EXTRA = 255;
+
+/*
+const uint8 FLAG_FIN = 0x01;
+const uint8 FLAG_SYN = 0x02;
+const uint8 FLAG_ACK = 0x10;
+*/
+
+const uint32 CTRL_BOUND = 0x80000000;
+
+const long DEFAULT_TIMEOUT = 4000; // If there are no pending clocks, wake up every 4 seconds
+const long CLOSED_TIMEOUT = 60 * 1000; // If the connection is closed, once per minute
+
+#if PSEUDO_KEEPALIVE
+// !?! Rethink these times
+const uint32 IDLE_PING = 20 * 1000; // 20 seconds (note: WinXP SP2 firewall udp timeout is 90 seconds)
+const uint32 IDLE_TIMEOUT = 90 * 1000; // 90 seconds;
+#endif // PSEUDO_KEEPALIVE
+
+//////////////////////////////////////////////////////////////////////
+// Helper Functions
+//////////////////////////////////////////////////////////////////////
+
+inline void long_to_bytes(uint32 val, void* buf) {
+  *static_cast<uint32*>(buf) = talk_base::HostToNetwork32(val);
+}
+
+inline void short_to_bytes(uint16 val, void* buf) {
+  *static_cast<uint16*>(buf) = talk_base::HostToNetwork16(val);
+}
+
+inline uint32 bytes_to_long(const void* buf) {
+  return talk_base::NetworkToHost32(*static_cast<const uint32*>(buf));
+}
+
+inline uint16 bytes_to_short(const void* buf) {
+  return talk_base::NetworkToHost16(*static_cast<const uint16*>(buf));
+}
+
+uint32 bound(uint32 lower, uint32 middle, uint32 upper) {
+  return talk_base::_min(talk_base::_max(lower, middle), upper);
+}
+
+//////////////////////////////////////////////////////////////////////
+// Debugging Statistics
+//////////////////////////////////////////////////////////////////////
+
+#if 0  // Not used yet
+
+enum Stat {
+  S_SENT_PACKET,   // All packet sends
+  S_RESENT_PACKET, // All packet sends that are retransmits
+  S_RECV_PACKET,   // All packet receives
+  S_RECV_NEW,      // All packet receives that are too new
+  S_RECV_OLD,      // All packet receives that are too old
+  S_NUM_STATS
+};
+
+const char* const STAT_NAMES[S_NUM_STATS] = {
+  "snt",
+  "snt-r",
+  "rcv"
+  "rcv-n",
+  "rcv-o"
+};
+
+int g_stats[S_NUM_STATS];
+inline void Incr(Stat s) { ++g_stats[s]; }
+void ReportStats() {
+  char buffer[256];
+  size_t len = 0;
+  for (int i = 0; i < S_NUM_STATS; ++i) {
+    len += talk_base::sprintfn(buffer, ARRAY_SIZE(buffer), "%s%s:%d",
+                               (i == 0) ? "" : ",", STAT_NAMES[i], g_stats[i]);
+    g_stats[i] = 0;
+  }
+  LOG(LS_INFO) << "Stats[" << buffer << "]";
+}
+
+#endif
+
+//////////////////////////////////////////////////////////////////////
+// PseudoTcp
+//////////////////////////////////////////////////////////////////////
+
+uint32 PseudoTcp::Now() {
+#if 0  // Use this to synchronize timers with logging timestamps (easier debug)
+  return talk_base::TimeSince(StartTime());
+#else
+  return talk_base::Time();
+#endif
+}
+
+PseudoTcp::PseudoTcp(IPseudoTcpNotify* notify, uint32 conv)
+    : m_notify(notify), m_shutdown(SD_NONE), m_error(0) {
+
+  // Sanity check on buffer sizes (needed for OnTcpWriteable notification logic)
+  ASSERT(sizeof(m_rbuf) + MIN_PACKET < sizeof(m_sbuf));
+
+  uint32 now = Now();
+
+  m_state = TCP_LISTEN;
+  m_conv = conv;
+  m_rcv_wnd = sizeof(m_rbuf);
+  m_snd_nxt = m_slen = 0;
+  m_snd_wnd = 1;
+  m_snd_una = m_rcv_nxt = m_rlen = 0;
+  m_bReadEnable = true;
+  m_bWriteEnable = false;
+  m_t_ack = 0;
+
+  m_msslevel = 0;
+  m_largest = 0;
+  ASSERT(MIN_PACKET > PACKET_OVERHEAD);
+  m_mss = MIN_PACKET - PACKET_OVERHEAD;
+  m_mtu_advise = MAX_PACKET;
+
+  m_rto_base = 0;
+
+  m_cwnd = 2 * m_mss;
+  m_ssthresh = sizeof(m_rbuf);
+  m_lastrecv = m_lastsend = m_lasttraffic = now;
+  m_bOutgoing = false;
+
+  m_dup_acks = 0;
+  m_recover = 0;
+
+  m_ts_recent = m_ts_lastack = 0;
+
+  m_rx_rto = DEF_RTO;
+  m_rx_srtt = m_rx_rttvar = 0;
+}
+
+PseudoTcp::~PseudoTcp() {
+}
+
+int PseudoTcp::Connect() {
+  if (m_state != TCP_LISTEN) {
+    m_error = EINVAL;
+    return -1;
+  }
+
+  m_state = TCP_SYN_SENT;
+  LOG(LS_INFO) << "State: TCP_SYN_SENT";
+
+  char buffer[1];
+  buffer[0] = CTL_CONNECT;
+  queue(buffer, 1, true);
+  attemptSend();
+
+  return 0;
+}
+
+void PseudoTcp::NotifyMTU(uint16 mtu) {
+  m_mtu_advise = mtu;
+  if (m_state == TCP_ESTABLISHED) {
+    adjustMTU();
+  }
+}
+
+void PseudoTcp::NotifyClock(uint32 now) {
+  if (m_state == TCP_CLOSED)
+    return;
+
+    // Check if it's time to retransmit a segment
+  if (m_rto_base && (talk_base::TimeDiff(m_rto_base + m_rx_rto, now) <= 0)) {
+    if (m_slist.empty()) {
+      ASSERT(false);
+    } else {
+      // Note: (m_slist.front().xmit == 0)) {
+      // retransmit segments
+#if _DEBUGMSG >= _DBG_NORMAL
+      LOG(LS_INFO) << "timeout retransmit (rto: " << m_rx_rto
+                   << ") (rto_base: " << m_rto_base
+                   << ") (now: " << now
+                   << ") (dup_acks: " << static_cast<unsigned>(m_dup_acks)
+                   << ")";
+#endif // _DEBUGMSG
+      if (!transmit(m_slist.begin(), now)) {
+        closedown(ECONNABORTED);
+        return;
+      }
+
+      uint32 nInFlight = m_snd_nxt - m_snd_una;
+      m_ssthresh = talk_base::_max(nInFlight / 2, 2 * m_mss);
+      //LOG(LS_INFO) << "m_ssthresh: " << m_ssthresh << "  nInFlight: " << nInFlight << "  m_mss: " << m_mss;
+      m_cwnd = m_mss;
+
+      // Back off retransmit timer.  Note: the limit is lower when connecting.
+      uint32 rto_limit = (m_state < TCP_ESTABLISHED) ? DEF_RTO : MAX_RTO;
+      m_rx_rto = talk_base::_min(rto_limit, m_rx_rto * 2);
+      m_rto_base = now;
+    }
+  }
+
+  // Check if it's time to probe closed windows
+  if ((m_snd_wnd == 0)
+        && (talk_base::TimeDiff(m_lastsend + m_rx_rto, now) <= 0)) {
+    if (talk_base::TimeDiff(now, m_lastrecv) >= 15000) {
+      closedown(ECONNABORTED);
+      return;
+    }
+
+    // probe the window
+    packet(m_snd_nxt - 1, 0, 0, 0);
+    m_lastsend = now;
+
+    // back off retransmit timer
+    m_rx_rto = talk_base::_min(MAX_RTO, m_rx_rto * 2);
+  }
+
+  // Check if it's time to send delayed acks
+  if (m_t_ack && (talk_base::TimeDiff(m_t_ack + ACK_DELAY, now) <= 0)) {
+    packet(m_snd_nxt, 0, 0, 0);
+  }
+
+#if PSEUDO_KEEPALIVE
+  // Check for idle timeout
+  if ((m_state == TCP_ESTABLISHED) && (TimeDiff(m_lastrecv + IDLE_TIMEOUT, now) <= 0)) {
+    closedown(ECONNABORTED);
+    return;
+  }
+
+  // Check for ping timeout (to keep udp mapping open)
+  if ((m_state == TCP_ESTABLISHED) && (TimeDiff(m_lasttraffic + (m_bOutgoing ? IDLE_PING * 3/2 : IDLE_PING), now) <= 0)) {
+    packet(m_snd_nxt, 0, 0, 0);
+  }
+#endif // PSEUDO_KEEPALIVE
+}
+
+bool PseudoTcp::NotifyPacket(const char* buffer, size_t len) {
+  if (len > MAX_PACKET) {
+    LOG_F(WARNING) << "packet too large";
+    return false;
+  }
+  return parse(reinterpret_cast<const uint8 *>(buffer), uint32(len));
+}
+
+bool PseudoTcp::GetNextClock(uint32 now, long& timeout) {
+  return clock_check(now, timeout);
+}
+
+//
+// IPStream Implementation
+//
+
+int PseudoTcp::Recv(char* buffer, size_t len) {
+  if (m_state != TCP_ESTABLISHED) {
+    m_error = ENOTCONN;
+    return SOCKET_ERROR;
+  }
+
+  if (m_rlen == 0) {
+    m_bReadEnable = true;
+    m_error = EWOULDBLOCK;
+    return SOCKET_ERROR;
+  }
+
+  uint32 read = talk_base::_min(uint32(len), m_rlen);
+  memcpy(buffer, m_rbuf, read);
+  m_rlen -= read;
+
+  // !?! until we create a circular buffer, we need to move all of the rest of the buffer up!
+  memmove(m_rbuf, m_rbuf + read, sizeof(m_rbuf) - read/*m_rlen*/);
+
+  if ((sizeof(m_rbuf) - m_rlen - m_rcv_wnd)
+      >= talk_base::_min<uint32>(sizeof(m_rbuf) / 2, m_mss)) {
+    bool bWasClosed = (m_rcv_wnd == 0); // !?! Not sure about this was closed business
+
+    m_rcv_wnd = sizeof(m_rbuf) - m_rlen;
+
+    if (bWasClosed) {
+      attemptSend(sfImmediateAck);
+    }
+  }
+
+  return read;
+}
+
+int PseudoTcp::Send(const char* buffer, size_t len) {
+  if (m_state != TCP_ESTABLISHED) {
+    m_error = ENOTCONN;
+    return SOCKET_ERROR;
+  }
+
+  if (m_slen == sizeof(m_sbuf)) {
+    m_bWriteEnable = true;
+    m_error = EWOULDBLOCK;
+    return SOCKET_ERROR;
+  }
+
+  int written = queue(buffer, uint32(len), false);
+  attemptSend();
+  return written;
+}
+
+void PseudoTcp::Close(bool force) {
+  LOG_F(LS_VERBOSE) << "(" << (force ? "true" : "false") << ")";
+  m_shutdown = force ? SD_FORCEFUL : SD_GRACEFUL;
+}
+
+int PseudoTcp::GetError() {
+  return m_error;
+}
+
+//
+// Internal Implementation
+//
+
+uint32 PseudoTcp::queue(const char* data, uint32 len, bool bCtrl) {
+  if (len > sizeof(m_sbuf) - m_slen) {
+    ASSERT(!bCtrl);
+    len = sizeof(m_sbuf) - m_slen;
+  }
+
+  // We can concatenate data if the last segment is the same type
+  // (control v. regular data), and has not been transmitted yet
+  if (!m_slist.empty() && (m_slist.back().bCtrl == bCtrl) && (m_slist.back().xmit == 0)) {
+    m_slist.back().len += len;
+  } else {
+    SSegment sseg(m_snd_una + m_slen, len, bCtrl);
+    m_slist.push_back(sseg);
+  }
+
+  memcpy(m_sbuf + m_slen, data, len);
+  m_slen += len;
+  //LOG(LS_INFO) << "PseudoTcp::queue - m_slen = " << m_slen;
+  return len;
+}
+
+IPseudoTcpNotify::WriteResult PseudoTcp::packet(uint32 seq, uint8 flags,
+                                                const char* data, uint32 len) {
+  ASSERT(HEADER_SIZE + len <= MAX_PACKET);
+
+  uint32 now = Now();
+
+  uint8 buffer[MAX_PACKET];
+  long_to_bytes(m_conv, buffer);
+  long_to_bytes(seq, buffer + 4);
+  long_to_bytes(m_rcv_nxt, buffer + 8);
+  buffer[12] = 0;
+  buffer[13] = flags;
+  short_to_bytes(uint16(m_rcv_wnd), buffer + 14);
+
+  // Timestamp computations
+  long_to_bytes(now, buffer + 16);
+  long_to_bytes(m_ts_recent, buffer + 20);
+  m_ts_lastack = m_rcv_nxt;
+
+  memcpy(buffer + HEADER_SIZE, data, len);
+
+#if _DEBUGMSG >= _DBG_VERBOSE
+  LOG(LS_INFO) << "<-- <CONV=" << m_conv
+               << "><FLG=" << static_cast<unsigned>(flags)
+               << "><SEQ=" << seq << ":" << seq + len
+               << "><ACK=" << m_rcv_nxt
+               << "><WND=" << m_rcv_wnd
+               << "><TS="  << (now % 10000)
+               << "><TSR=" << (m_ts_recent % 10000)
+               << "><LEN=" << len << ">";
+#endif // _DEBUGMSG
+
+  IPseudoTcpNotify::WriteResult wres = m_notify->TcpWritePacket(this, reinterpret_cast<char *>(buffer), len + HEADER_SIZE);
+  // Note: When data is NULL, this is an ACK packet.  We don't read the return value for those,
+  // and thus we won't retry.  So go ahead and treat the packet as a success (basically simulate
+  // as if it were dropped), which will prevent our timers from being messed up.
+  if ((wres != IPseudoTcpNotify::WR_SUCCESS) && (NULL != data))
+    return wres;
+
+  m_t_ack = 0;
+  if (len > 0) {
+    m_lastsend = now;
+  }
+  m_lasttraffic = now;
+  m_bOutgoing = true;
+
+  return IPseudoTcpNotify::WR_SUCCESS;
+}
+
+bool PseudoTcp::parse(const uint8* buffer, uint32 size) {
+  if (size < 12)
+    return false;
+
+  Segment seg;
+  seg.conv = bytes_to_long(buffer);
+  seg.seq = bytes_to_long(buffer + 4);
+  seg.ack = bytes_to_long(buffer + 8);
+  seg.flags = buffer[13];
+  seg.wnd = bytes_to_short(buffer + 14);
+
+  seg.tsval = bytes_to_long(buffer + 16);
+  seg.tsecr = bytes_to_long(buffer + 20);
+
+  seg.data = reinterpret_cast<const char *>(buffer) + HEADER_SIZE;
+  seg.len = size - HEADER_SIZE;
+
+#if _DEBUGMSG >= _DBG_VERBOSE
+  LOG(LS_INFO) << "--> <CONV=" << seg.conv
+               << "><FLG=" << static_cast<unsigned>(seg.flags)
+               << "><SEQ=" << seg.seq << ":" << seg.seq + seg.len
+               << "><ACK=" << seg.ack
+               << "><WND=" << seg.wnd
+               << "><TS="  << (seg.tsval % 10000)
+               << "><TSR=" << (seg.tsecr % 10000)
+               << "><LEN=" << seg.len << ">";
+#endif // _DEBUGMSG
+
+  return process(seg);
+}
+
+bool PseudoTcp::clock_check(uint32 now, long& nTimeout) {
+  if (m_shutdown == SD_FORCEFUL)
+    return false;
+
+  if ((m_shutdown == SD_GRACEFUL)
+      && ((m_state != TCP_ESTABLISHED)
+          || ((m_slen == 0) && (m_t_ack == 0)))) {
+    return false;
+  }
+
+  if (m_state == TCP_CLOSED) {
+    nTimeout = CLOSED_TIMEOUT;
+    return true;
+  }
+
+  nTimeout = DEFAULT_TIMEOUT;
+
+  if (m_t_ack) {
+    nTimeout = talk_base::_min<int32>(nTimeout,
+      talk_base::TimeDiff(m_t_ack + ACK_DELAY, now));
+  }
+  if (m_rto_base) {
+    nTimeout = talk_base::_min<int32>(nTimeout,
+      talk_base::TimeDiff(m_rto_base + m_rx_rto, now));
+  }
+  if (m_snd_wnd == 0) {
+    nTimeout = talk_base::_min<int32>(nTimeout, talk_base::TimeDiff(m_lastsend + m_rx_rto, now));
+  }
+#if PSEUDO_KEEPALIVE
+  if (m_state == TCP_ESTABLISHED) {
+    nTimeout = talk_base::_min<int32>(nTimeout,
+      talk_base::TimeDiff(m_lasttraffic + (m_bOutgoing ? IDLE_PING * 3/2 : IDLE_PING), now));
+  }
+#endif // PSEUDO_KEEPALIVE
+  return true;
+}
+
+bool PseudoTcp::process(Segment& seg) {
+  // If this is the wrong conversation, send a reset!?! (with the correct conversation?)
+  if (seg.conv != m_conv) {
+    //if ((seg.flags & FLAG_RST) == 0) {
+    //  packet(tcb, seg.ack, 0, FLAG_RST, 0, 0);
+    //}
+    LOG_F(LS_ERROR) << "wrong conversation";
+    return false;
+  }
+
+  uint32 now = Now();
+  m_lasttraffic = m_lastrecv = now;
+  m_bOutgoing = false;
+
+  if (m_state == TCP_CLOSED) {
+    // !?! send reset?
+    LOG_F(LS_ERROR) << "closed";
+    return false;
+  }
+
+  // Check if this is a reset segment
+  if (seg.flags & FLAG_RST) {
+    closedown(ECONNRESET);
+    return false;
+  }
+
+  // Check for control data
+  bool bConnect = false;
+  if (seg.flags & FLAG_CTL) {
+    if (seg.len == 0) {
+      LOG_F(LS_ERROR) << "Missing control code";
+      return false;
+    } else if (seg.data[0] == CTL_CONNECT) {
+      bConnect = true;
+      if (m_state == TCP_LISTEN) {
+        m_state = TCP_SYN_RECEIVED;
+        LOG(LS_INFO) << "State: TCP_SYN_RECEIVED";
+        //m_notify->associate(addr);
+        char buffer[1];
+        buffer[0] = CTL_CONNECT;
+        queue(buffer, 1, true);
+      } else if (m_state == TCP_SYN_SENT) {
+        m_state = TCP_ESTABLISHED;
+        LOG(LS_INFO) << "State: TCP_ESTABLISHED";
+        adjustMTU();
+        if (m_notify) {
+          m_notify->OnTcpOpen(this);
+        }
+        //notify(evOpen);
+      }
+    } else {
+      LOG_F(LS_WARNING) << "Unknown control code: " << seg.data[0];
+      return false;
+    }
+  }
+
+  // Update timestamp
+  if ((seg.seq <= m_ts_lastack) && (m_ts_lastack < seg.seq + seg.len)) {
+    m_ts_recent = seg.tsval;
+  }
+
+  // Check if this is a valuable ack
+  if ((seg.ack > m_snd_una) && (seg.ack <= m_snd_nxt)) {
+    // Calculate round-trip time
+    if (seg.tsecr) {
+      long rtt = talk_base::TimeDiff(now, seg.tsecr);
+      if (rtt >= 0) {
+        if (m_rx_srtt == 0) {
+          m_rx_srtt = rtt;
+          m_rx_rttvar = rtt / 2;
+        } else {
+          m_rx_rttvar = (3 * m_rx_rttvar + abs(long(rtt - m_rx_srtt))) / 4;
+          m_rx_srtt = (7 * m_rx_srtt + rtt) / 8;
+        }
+        m_rx_rto = bound(MIN_RTO, m_rx_srtt +
+            talk_base::_max<uint32>(1, 4 * m_rx_rttvar), MAX_RTO);
+#if _DEBUGMSG >= _DBG_VERBOSE
+        LOG(LS_INFO) << "rtt: " << rtt
+                     << "  srtt: " << m_rx_srtt
+                     << "  rto: " << m_rx_rto;
+#endif // _DEBUGMSG
+      } else {
+        ASSERT(false);
+      }
+    }
+
+    m_snd_wnd = seg.wnd;
+
+    uint32 nAcked = seg.ack - m_snd_una;
+    m_snd_una = seg.ack;
+
+    m_rto_base = (m_snd_una == m_snd_nxt) ? 0 : now;
+
+    m_slen -= nAcked;
+    memmove(m_sbuf, m_sbuf + nAcked, m_slen);
+    //LOG(LS_INFO) << "PseudoTcp::process - m_slen = " << m_slen;
+
+    for (uint32 nFree = nAcked; nFree > 0; ) {
+      ASSERT(!m_slist.empty());
+      if (nFree < m_slist.front().len) {
+        m_slist.front().len -= nFree;
+        nFree = 0;
+      } else {
+        if (m_slist.front().len > m_largest) {
+          m_largest = m_slist.front().len;
+        }
+        nFree -= m_slist.front().len;
+        m_slist.pop_front();
+      }
+    }
+
+    if (m_dup_acks >= 3) {
+      if (m_snd_una >= m_recover) { // NewReno
+        uint32 nInFlight = m_snd_nxt - m_snd_una;
+        m_cwnd = talk_base::_min(m_ssthresh, nInFlight + m_mss); // (Fast Retransmit)
+#if _DEBUGMSG >= _DBG_NORMAL
+        LOG(LS_INFO) << "exit recovery";
+#endif // _DEBUGMSG
+        m_dup_acks = 0;
+      } else {
+#if _DEBUGMSG >= _DBG_NORMAL
+        LOG(LS_INFO) << "recovery retransmit";
+#endif // _DEBUGMSG
+        if (!transmit(m_slist.begin(), now)) {
+          closedown(ECONNABORTED);
+          return false;
+        }
+        m_cwnd += m_mss - talk_base::_min(nAcked, m_cwnd);
+      }
+    } else {
+      m_dup_acks = 0;
+      // Slow start, congestion avoidance
+      if (m_cwnd < m_ssthresh) {
+        m_cwnd += m_mss;
+      } else {
+        m_cwnd += talk_base::_max<uint32>(1, m_mss * m_mss / m_cwnd);
+      }
+    }
+
+    // !?! A bit hacky
+    if ((m_state == TCP_SYN_RECEIVED) && !bConnect) {
+      m_state = TCP_ESTABLISHED;
+      LOG(LS_INFO) << "State: TCP_ESTABLISHED";
+      adjustMTU();
+      if (m_notify) {
+        m_notify->OnTcpOpen(this);
+      }
+      //notify(evOpen);
+    }
+
+    // If we make room in the send queue, notify the user
+    // The goal it to make sure we always have at least enough data to fill the
+    // window.  We'd like to notify the app when we are halfway to that point.
+    const uint32 kIdealRefillSize = (sizeof(m_sbuf) + sizeof(m_rbuf)) / 2;
+    if (m_bWriteEnable && (m_slen < kIdealRefillSize)) {
+      m_bWriteEnable = false;
+      if (m_notify) {
+        m_notify->OnTcpWriteable(this);
+      }
+      //notify(evWrite);
+    }
+  } else if (seg.ack == m_snd_una) {
+    // !?! Note, tcp says don't do this... but otherwise how does a closed window become open?
+    m_snd_wnd = seg.wnd;
+
+    // Check duplicate acks
+    if (seg.len > 0) {
+      // it's a dup ack, but with a data payload, so don't modify m_dup_acks
+    } else if (m_snd_una != m_snd_nxt) {
+      m_dup_acks += 1;
+      if (m_dup_acks == 3) { // (Fast Retransmit)
+#if _DEBUGMSG >= _DBG_NORMAL
+        LOG(LS_INFO) << "enter recovery";
+        LOG(LS_INFO) << "recovery retransmit";
+#endif // _DEBUGMSG
+        if (!transmit(m_slist.begin(), now)) {
+          closedown(ECONNABORTED);
+          return false;
+        }
+        m_recover = m_snd_nxt;
+        uint32 nInFlight = m_snd_nxt - m_snd_una;
+        m_ssthresh = talk_base::_max(nInFlight / 2, 2 * m_mss);
+        //LOG(LS_INFO) << "m_ssthresh: " << m_ssthresh << "  nInFlight: " << nInFlight << "  m_mss: " << m_mss;
+        m_cwnd = m_ssthresh + 3 * m_mss;
+      } else if (m_dup_acks > 3) {
+        m_cwnd += m_mss;
+      }
+    } else {
+      m_dup_acks = 0;
+    }
+  }
+
+  // Conditions were acks must be sent:
+  // 1) Segment is too old (they missed an ACK) (immediately)
+  // 2) Segment is too new (we missed a segment) (immediately)
+  // 3) Segment has data (so we need to ACK!) (delayed)
+  // ... so the only time we don't need to ACK, is an empty segment that points to rcv_nxt!
+
+  SendFlags sflags = sfNone;
+  if (seg.seq != m_rcv_nxt) {
+    sflags = sfImmediateAck; // (Fast Recovery)
+  } else if (seg.len != 0) {
+    sflags = sfDelayedAck;
+  }
+#if _DEBUGMSG >= _DBG_NORMAL
+  if (sflags == sfImmediateAck) {
+    if (seg.seq > m_rcv_nxt) {
+      LOG_F(LS_INFO) << "too new";
+    } else if (seg.seq + seg.len <= m_rcv_nxt) {
+      LOG_F(LS_INFO) << "too old";
+    }
+  }
+#endif // _DEBUGMSG
+
+  // Adjust the incoming segment to fit our receive buffer
+  if (seg.seq < m_rcv_nxt) {
+    uint32 nAdjust = m_rcv_nxt - seg.seq;
+    if (nAdjust < seg.len) {
+      seg.seq += nAdjust;
+      seg.data += nAdjust;
+      seg.len -= nAdjust;
+    } else {
+      seg.len = 0;
+    }
+  }
+  if ((seg.seq + seg.len - m_rcv_nxt) > (sizeof(m_rbuf) - m_rlen)) {
+    uint32 nAdjust = seg.seq + seg.len - m_rcv_nxt - (sizeof(m_rbuf) - m_rlen);
+    if (nAdjust < seg.len) {
+      seg.len -= nAdjust;
+    } else {
+      seg.len = 0;
+    }
+  }
+
+  bool bIgnoreData = (seg.flags & FLAG_CTL) || (m_shutdown != SD_NONE);
+  bool bNewData = false;
+
+  if (seg.len > 0) {
+    if (bIgnoreData) {
+      if (seg.seq == m_rcv_nxt) {
+        m_rcv_nxt += seg.len;
+      }
+    } else {
+      uint32 nOffset = seg.seq - m_rcv_nxt;
+      memcpy(m_rbuf + m_rlen + nOffset, seg.data, seg.len);
+      if (seg.seq == m_rcv_nxt) {
+        m_rlen += seg.len;
+        m_rcv_nxt += seg.len;
+        m_rcv_wnd -= seg.len;
+        bNewData = true;
+
+        RList::iterator it = m_rlist.begin();
+        while ((it != m_rlist.end()) && (it->seq <= m_rcv_nxt)) {
+          if (it->seq + it->len > m_rcv_nxt) {
+            sflags = sfImmediateAck; // (Fast Recovery)
+            uint32 nAdjust = (it->seq + it->len) - m_rcv_nxt;
+#if _DEBUGMSG >= _DBG_NORMAL
+            LOG(LS_INFO) << "Recovered " << nAdjust << " bytes (" << m_rcv_nxt << " -> " << m_rcv_nxt + nAdjust << ")";
+#endif // _DEBUGMSG
+            m_rlen += nAdjust;
+            m_rcv_nxt += nAdjust;
+            m_rcv_wnd -= nAdjust;
+          }
+          it = m_rlist.erase(it);
+        }
+      } else {
+#if _DEBUGMSG >= _DBG_NORMAL
+        LOG(LS_INFO) << "Saving " << seg.len << " bytes (" << seg.seq << " -> " << seg.seq + seg.len << ")";
+#endif // _DEBUGMSG
+        RSegment rseg;
+        rseg.seq = seg.seq;
+        rseg.len = seg.len;
+        RList::iterator it = m_rlist.begin();
+        while ((it != m_rlist.end()) && (it->seq < rseg.seq)) {
+          ++it;
+        }
+        m_rlist.insert(it, rseg);
+      }
+    }
+  }
+
+  attemptSend(sflags);
+
+  // If we have new data, notify the user
+  if (bNewData && m_bReadEnable) {
+    m_bReadEnable = false;
+    if (m_notify) {
+      m_notify->OnTcpReadable(this);
+    }
+    //notify(evRead);
+  }
+
+  return true;
+}
+
+bool PseudoTcp::transmit(const SList::iterator& seg, uint32 now) {
+  if (seg->xmit >= ((m_state == TCP_ESTABLISHED) ? 15 : 30)) {
+    LOG_F(LS_VERBOSE) << "too many retransmits";
+    return false;
+  }
+
+  uint32 nTransmit = talk_base::_min(seg->len, m_mss);
+
+  while (true) {
+    uint32 seq = seg->seq;
+    uint8 flags = (seg->bCtrl ? FLAG_CTL : 0);
+    const char* buffer = m_sbuf + (seg->seq - m_snd_una);
+    IPseudoTcpNotify::WriteResult wres = this->packet(seq, flags, buffer, nTransmit);
+
+    if (wres == IPseudoTcpNotify::WR_SUCCESS)
+      break;
+
+    if (wres == IPseudoTcpNotify::WR_FAIL) {
+      LOG_F(LS_VERBOSE) << "packet failed";
+      return false;
+    }
+
+    ASSERT(wres == IPseudoTcpNotify::WR_TOO_LARGE);
+
+    while (true) {
+      if (PACKET_MAXIMUMS[m_msslevel + 1] == 0) {
+        LOG_F(LS_VERBOSE) << "MTU too small";
+        return false;
+      }
+      // !?! We need to break up all outstanding and pending packets and then retransmit!?!
+
+      m_mss = PACKET_MAXIMUMS[++m_msslevel] - PACKET_OVERHEAD;
+      m_cwnd = 2 * m_mss; // I added this... haven't researched actual formula
+      if (m_mss < nTransmit) {
+        nTransmit = m_mss;
+        break;
+      }
+    }
+#if _DEBUGMSG >= _DBG_NORMAL
+    LOG(LS_INFO) << "Adjusting mss to " << m_mss << " bytes";
+#endif // _DEBUGMSG
+  }
+
+  if (nTransmit < seg->len) {
+    LOG_F(LS_VERBOSE) << "mss reduced to " << m_mss;
+
+    SSegment subseg(seg->seq + nTransmit, seg->len - nTransmit, seg->bCtrl);
+    //subseg.tstamp = seg->tstamp;
+    subseg.xmit = seg->xmit;
+    seg->len = nTransmit;
+
+    SList::iterator next = seg;
+    m_slist.insert(++next, subseg);
+  }
+
+  if (seg->xmit == 0) {
+    m_snd_nxt += seg->len;
+  }
+  seg->xmit += 1;
+  //seg->tstamp = now;
+  if (m_rto_base == 0) {
+    m_rto_base = now;
+  }
+
+  return true;
+}
+
+void PseudoTcp::attemptSend(SendFlags sflags) {
+  uint32 now = Now();
+
+  if (talk_base::TimeDiff(now, m_lastsend) > static_cast<long>(m_rx_rto)) {
+    m_cwnd = m_mss;
+  }
+
+#if _DEBUGMSG
+  bool bFirst = true;
+  UNUSED(bFirst);
+#endif // _DEBUGMSG
+
+  while (true) {
+    uint32 cwnd = m_cwnd;
+    if ((m_dup_acks == 1) || (m_dup_acks == 2)) { // Limited Transmit
+      cwnd += m_dup_acks * m_mss;
+    }
+    uint32 nWindow = talk_base::_min(m_snd_wnd, cwnd);
+    uint32 nInFlight = m_snd_nxt - m_snd_una;
+    uint32 nUseable = (nInFlight < nWindow) ? (nWindow - nInFlight) : 0;
+
+    uint32 nAvailable = talk_base::_min(m_slen - nInFlight, m_mss);
+
+    if (nAvailable > nUseable) {
+      if (nUseable * 4 < nWindow) {
+        // RFC 813 - avoid SWS
+        nAvailable = 0;
+      } else {
+        nAvailable = nUseable;
+      }
+    }
+
+#if _DEBUGMSG >= _DBG_VERBOSE
+    if (bFirst) {
+      bFirst = false;
+      LOG(LS_INFO) << "[cwnd: " << m_cwnd
+                   << "  nWindow: " << nWindow
+                   << "  nInFlight: " << nInFlight
+                   << "  nAvailable: " << nAvailable
+                   << "  nQueued: " << m_slen - nInFlight
+                   << "  nEmpty: " << sizeof(m_sbuf) - m_slen
+                   << "  ssthresh: " << m_ssthresh << "]";
+    }
+#endif // _DEBUGMSG
+
+    if (nAvailable == 0) {
+      if (sflags == sfNone)
+        return;
+
+      // If this is an immediate ack, or the second delayed ack
+      if ((sflags == sfImmediateAck) || m_t_ack) {
+        packet(m_snd_nxt, 0, 0, 0);
+      } else {
+        m_t_ack = Now();
+      }
+      return;
+    }
+
+    // Nagle algorithm
+    if ((m_snd_nxt > m_snd_una) && (nAvailable < m_mss))  {
+      return;
+    }
+
+    // Find the next segment to transmit
+    SList::iterator it = m_slist.begin();
+    while (it->xmit > 0) {
+      ++it;
+      ASSERT(it != m_slist.end());
+    }
+    SList::iterator seg = it;
+
+    // If the segment is too large, break it into two
+    if (seg->len > nAvailable) {
+      SSegment subseg(seg->seq + nAvailable, seg->len - nAvailable, seg->bCtrl);
+      seg->len = nAvailable;
+      m_slist.insert(++it, subseg);
+    }
+
+    if (!transmit(seg, now)) {
+      LOG_F(LS_VERBOSE) << "transmit failed";
+      // TODO: consider closing socket
+      return;
+    }
+
+    sflags = sfNone;
+  }
+}
+
+void
+PseudoTcp::closedown(uint32 err) {
+  m_slen = 0;
+
+  LOG(LS_INFO) << "State: TCP_CLOSED";
+  m_state = TCP_CLOSED;
+  if (m_notify) {
+    m_notify->OnTcpClosed(this, err);
+  }
+  //notify(evClose, err);
+}
+
+void
+PseudoTcp::adjustMTU() {
+  // Determine our current mss level, so that we can adjust appropriately later
+  for (m_msslevel = 0; PACKET_MAXIMUMS[m_msslevel + 1] > 0; ++m_msslevel) {
+    if (static_cast<uint16>(PACKET_MAXIMUMS[m_msslevel]) <= m_mtu_advise) {
+      break;
+    }
+  }
+  m_mss = m_mtu_advise - PACKET_OVERHEAD;
+  // !?! Should we reset m_largest here?
+#if _DEBUGMSG >= _DBG_NORMAL
+  LOG(LS_INFO) << "Adjusting mss to " << m_mss << " bytes";
+#endif // _DEBUGMSG
+  // Enforce minimums on ssthresh and cwnd
+  m_ssthresh = talk_base::_max(m_ssthresh, 2 * m_mss);
+  m_cwnd = talk_base::_max(m_cwnd, m_mss);
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/pseudotcp.h b/talk/p2p/base/pseudotcp.h
new file mode 100644
index 0000000..1446201
--- /dev/null
+++ b/talk/p2p/base/pseudotcp.h
@@ -0,0 +1,187 @@
+/*
+ * 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_P2P_BASE_PSEUDOTCP_H_
+#define TALK_P2P_BASE_PSEUDOTCP_H_
+
+#include <list>
+
+#include "talk/base/basictypes.h"
+
+namespace cricket {
+
+//////////////////////////////////////////////////////////////////////
+// IPseudoTcpNotify
+//////////////////////////////////////////////////////////////////////
+
+class PseudoTcp;
+
+class IPseudoTcpNotify {
+ public:
+  virtual ~IPseudoTcpNotify() {}
+  // Notification of tcp events
+  virtual void OnTcpOpen(PseudoTcp* tcp) = 0;
+  virtual void OnTcpReadable(PseudoTcp* tcp) = 0;
+  virtual void OnTcpWriteable(PseudoTcp* tcp) = 0;
+  virtual void OnTcpClosed(PseudoTcp* tcp, uint32 error) = 0;
+
+  // Write the packet onto the network
+  enum WriteResult { WR_SUCCESS, WR_TOO_LARGE, WR_FAIL };
+  virtual WriteResult TcpWritePacket(PseudoTcp* tcp,
+                                     const char* buffer, size_t len) = 0;
+};
+
+//////////////////////////////////////////////////////////////////////
+// PseudoTcp
+//////////////////////////////////////////////////////////////////////
+
+class PseudoTcp {
+ public:
+  static uint32 Now();
+
+  PseudoTcp(IPseudoTcpNotify* notify, uint32 conv);
+  virtual ~PseudoTcp();
+
+  int Connect();
+  int Recv(char* buffer, size_t len);
+  int Send(const char* buffer, size_t len);
+  void Close(bool force);
+  int GetError();
+
+  enum TcpState {
+    TCP_LISTEN, TCP_SYN_SENT, TCP_SYN_RECEIVED, TCP_ESTABLISHED, TCP_CLOSED
+  };
+  TcpState State() const { return m_state; }
+
+  // Call this when the PMTU changes.
+  void NotifyMTU(uint16 mtu);
+
+  // Call this based on timeout value returned from GetNextClock.
+  // It's ok to call this too frequently.
+  void NotifyClock(uint32 now);
+
+  // Call this whenever a packet arrives.
+  // Returns true if the packet was processed successfully.
+  bool NotifyPacket(const char * buffer, size_t len);
+
+  // Call this to determine the next time NotifyClock should be called.
+  // Returns false if the socket is ready to be destroyed.
+  bool GetNextClock(uint32 now, long& timeout);
+
+ protected:
+  enum SendFlags { sfNone, sfDelayedAck, sfImmediateAck };
+  enum {
+    // Note: can't go as high as 1024 * 64, because of uint16 precision
+    kRcvBufSize = 1024 * 60,
+    // Note: send buffer should be larger to make sure we can always fill the
+    // receiver window
+    kSndBufSize = 1024 * 90
+  };
+
+  struct Segment {
+    uint32 conv, seq, ack;
+    uint8 flags;
+    uint16 wnd;
+    const char * data;
+    uint32 len;
+    uint32 tsval, tsecr;
+  };
+
+  struct SSegment {
+    SSegment(uint32 s, uint32 l, bool c)
+        : seq(s), len(l), /*tstamp(0),*/ xmit(0), bCtrl(c) {
+    }
+    uint32 seq, len;
+    //uint32 tstamp;
+    uint8 xmit;
+    bool bCtrl;
+  };
+  typedef std::list<SSegment> SList;
+
+  struct RSegment {
+    uint32 seq, len;
+  };
+
+  uint32 queue(const char* data, uint32 len, bool bCtrl);
+
+  IPseudoTcpNotify::WriteResult packet(uint32 seq, uint8 flags,
+                                       const char* data, uint32 len);
+  bool parse(const uint8* buffer, uint32 size);
+
+  void attemptSend(SendFlags sflags = sfNone);
+
+  void closedown(uint32 err = 0);
+
+  bool clock_check(uint32 now, long& nTimeout);
+
+  bool process(Segment& seg);
+  bool transmit(const SList::iterator& seg, uint32 now);
+
+  void adjustMTU();
+
+ private:
+  IPseudoTcpNotify* m_notify;
+  enum Shutdown { SD_NONE, SD_GRACEFUL, SD_FORCEFUL } m_shutdown;
+  int m_error;
+
+  // TCB data
+  TcpState m_state;
+  uint32 m_conv;
+  bool m_bReadEnable, m_bWriteEnable, m_bOutgoing;
+  uint32 m_lasttraffic;
+
+  // Incoming data
+  typedef std::list<RSegment> RList;
+  RList m_rlist;
+  char m_rbuf[kRcvBufSize];
+  uint32 m_rcv_nxt, m_rcv_wnd, m_rlen, m_lastrecv;
+
+  // Outgoing data
+  SList m_slist;
+  char m_sbuf[kSndBufSize];
+  uint32 m_snd_nxt, m_snd_wnd, m_slen, m_lastsend, m_snd_una;
+  // Maximum segment size, estimated protocol level, largest segment sent
+  uint32 m_mss, m_msslevel, m_largest, m_mtu_advise;
+  // Retransmit timer
+  uint32 m_rto_base;
+
+  // Timestamp tracking
+  uint32 m_ts_recent, m_ts_lastack;
+
+  // Round-trip calculation
+  uint32 m_rx_rttvar, m_rx_srtt, m_rx_rto;
+
+  // Congestion avoidance, Fast retransmit/recovery, Delayed ACKs
+  uint32 m_ssthresh, m_cwnd;
+  uint8 m_dup_acks;
+  uint32 m_recover;
+  uint32 m_t_ack;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_PSEUDOTCP_H_
diff --git a/talk/p2p/base/rawtransport.cc b/talk/p2p/base/rawtransport.cc
new file mode 100644
index 0000000..b07d69c
--- /dev/null
+++ b/talk/p2p/base/rawtransport.cc
@@ -0,0 +1,129 @@
+/*
+ * 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 <string>
+#include <vector>
+#include "talk/p2p/base/rawtransport.h"
+#include "talk/base/common.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/parsing.h"
+#include "talk/p2p/base/sessionmanager.h"
+#include "talk/p2p/base/rawtransportchannel.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/constants.h"
+
+#if defined(FEATURE_ENABLE_PSTN)
+namespace cricket {
+
+RawTransport::RawTransport(talk_base::Thread* signaling_thread,
+                           talk_base::Thread* worker_thread,
+                           PortAllocator* allocator)
+    : Transport(signaling_thread, worker_thread,
+                NS_GINGLE_RAW, allocator) {
+}
+
+RawTransport::~RawTransport() {
+  DestroyAllChannels();
+}
+
+bool RawTransport::ParseCandidates(SignalingProtocol protocol,
+                                   const buzz::XmlElement* elem,
+                                   Candidates* candidates,
+                                   ParseError* error) {
+  ASSERT(elem->FirstChild() == NULL);
+  for (const buzz::XmlElement* cand_elem = elem->FirstElement();
+       cand_elem != NULL;
+       cand_elem = cand_elem->NextElement()) {
+    if (cand_elem->Name() == QN_GINGLE_RAW_CHANNEL) {
+      talk_base::SocketAddress addr;
+      if (!ParseRawAddress(cand_elem, &addr, error))
+        return false;
+
+      Candidate candidate;
+      candidate.set_name(cand_elem->Attr(buzz::QN_NAME));
+      candidate.set_address(addr);
+      candidates->push_back(candidate);
+    }
+  }
+  return true;
+}
+
+bool RawTransport::WriteCandidates(SignalingProtocol protocol,
+                                   const Candidates& candidates,
+                                   XmlElements* candidate_elems,
+                                   WriteError* error) {
+  for (std::vector<Candidate>::const_iterator
+       cand = candidates.begin();
+       cand != candidates.end();
+       ++cand) {
+    ASSERT(cand->protocol() == "udp");
+    talk_base::SocketAddress addr = cand->address();
+
+    buzz::XmlElement* elem = new buzz::XmlElement(QN_GINGLE_RAW_CHANNEL);
+    elem->SetAttr(buzz::QN_NAME, type());
+    elem->SetAttr(QN_ADDRESS, addr.IPAsString());
+    elem->SetAttr(QN_PORT, addr.PortAsString());
+    candidate_elems->push_back(elem);
+  }
+  return true;
+}
+
+bool RawTransport::ParseRawAddress(const buzz::XmlElement* elem,
+                                   talk_base::SocketAddress* addr,
+                                   ParseError* error) {
+  // Make sure the required attributes exist
+  if (!elem->HasAttr(buzz::QN_NAME) ||
+      !elem->HasAttr(QN_ADDRESS) ||
+      !elem->HasAttr(QN_PORT)) {
+    return BadParse("channel missing required attribute", error);
+  }
+
+  // Make sure the channel named actually exists.
+  if (!HasChannel(elem->Attr(buzz::QN_NAME)))
+    return BadParse("channel named does not exist", error);
+
+  // Parse the address.
+  if (!ParseAddress(elem, QN_ADDRESS, QN_PORT, addr, error))
+    return false;
+
+  return true;
+}
+
+TransportChannelImpl* RawTransport::CreateTransportChannel(
+    const std::string& name, const std::string& content_type) {
+  return new RawTransportChannel(name, content_type, this,
+                                 worker_thread(),
+                                 port_allocator());
+}
+
+void RawTransport::DestroyTransportChannel(TransportChannelImpl* channel) {
+  delete channel;
+}
+
+}  // namespace cricket
+#endif  // defined(FEATURE_ENABLE_PSTN)
diff --git a/talk/p2p/base/rawtransport.h b/talk/p2p/base/rawtransport.h
new file mode 100644
index 0000000..6734130
--- /dev/null
+++ b/talk/p2p/base/rawtransport.h
@@ -0,0 +1,79 @@
+/*
+ * 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_P2P_BASE_RAWTRANSPORT_H_
+#define TALK_P2P_BASE_RAWTRANSPORT_H_
+
+#include <string>
+#include "talk/p2p/base/transport.h"
+
+#if defined(FEATURE_ENABLE_PSTN)
+namespace cricket {
+
+// Implements a transport that only sends raw packets, no STUN.  As a result,
+// it cannot do pings to determine connectivity, so it only uses a single port
+// that it thinks will work.
+class RawTransport: public Transport, public TransportParser {
+ public:
+  RawTransport(talk_base::Thread* signaling_thread,
+               talk_base::Thread* worker_thread,
+               PortAllocator* allocator);
+  virtual ~RawTransport();
+
+  virtual bool ParseCandidates(SignalingProtocol protocol,
+                               const buzz::XmlElement* elem,
+                               Candidates* candidates,
+                               ParseError* error);
+  virtual bool WriteCandidates(SignalingProtocol protocol,
+                               const Candidates& candidates,
+                               XmlElements* candidate_elems,
+                               WriteError* error);
+
+ protected:
+  // Creates and destroys raw channels.
+  virtual TransportChannelImpl* CreateTransportChannel(
+     const std::string& name, const std::string &content_type);
+  virtual void DestroyTransportChannel(TransportChannelImpl* channel);
+
+ private:
+  // Parses the given element, which should describe the address to use for a
+  // given channel.  This will return false and signal an error if the address
+  // or channel name is bad.
+  bool ParseRawAddress(const buzz::XmlElement* elem,
+                       talk_base::SocketAddress* addr,
+                       ParseError* error);
+
+  friend class RawTransportChannel;  // For ParseAddress.
+
+  DISALLOW_EVIL_CONSTRUCTORS(RawTransport);
+};
+
+}  // namespace cricket
+
+#endif  // defined(FEATURE_ENABLE_PSTN)
+
+#endif  // TALK_P2P_BASE_RAWTRANSPORT_H_
diff --git a/talk/p2p/base/rawtransportchannel.cc b/talk/p2p/base/rawtransportchannel.cc
new file mode 100644
index 0000000..39db13c
--- /dev/null
+++ b/talk/p2p/base/rawtransportchannel.cc
@@ -0,0 +1,278 @@
+/*
+ * 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/p2p/base/rawtransportchannel.h"
+
+#include <string>
+#include <vector>
+#include "talk/base/common.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/port.h"
+#include "talk/p2p/base/portallocator.h"
+#include "talk/p2p/base/rawtransport.h"
+#include "talk/p2p/base/relayport.h"
+#include "talk/p2p/base/sessionmanager.h"
+#include "talk/p2p/base/stunport.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/constants.h"
+
+#if defined(FEATURE_ENABLE_PSTN)
+
+namespace {
+
+const uint32 MSG_DESTROY_UNUSED_PORTS = 1;
+
+}  // namespace
+
+namespace cricket {
+
+RawTransportChannel::RawTransportChannel(const std::string &name,
+                                         const std::string &content_type,
+                                         RawTransport* transport,
+                                         talk_base::Thread *worker_thread,
+                                         PortAllocator *allocator)
+  : TransportChannelImpl(name, content_type),
+    raw_transport_(transport),
+    allocator_(allocator),
+    allocator_session_(NULL),
+    stun_port_(NULL),
+    relay_port_(NULL),
+    port_(NULL),
+    use_relay_(false) {
+  if (worker_thread == NULL)
+    worker_thread_ = raw_transport_->worker_thread();
+  else
+    worker_thread_ = worker_thread;
+}
+
+RawTransportChannel::~RawTransportChannel() {
+  delete allocator_session_;
+}
+
+int RawTransportChannel::SendPacket(const char *data, size_t size) {
+  if (port_ == NULL)
+    return -1;
+  if (remote_address_.IsAny())
+    return -1;
+  return port_->SendTo(data, size, remote_address_, true);
+}
+
+int RawTransportChannel::SetOption(talk_base::Socket::Option opt, int value) {
+  // TODO: allow these to be set before we have a port
+  if (port_ == NULL)
+    return -1;
+  return port_->SetOption(opt, value);
+}
+
+int RawTransportChannel::GetError() {
+  return (port_ != NULL) ? port_->GetError() : 0;
+}
+
+void RawTransportChannel::Connect() {
+  // Create an allocator that only returns stun and relay ports.
+  allocator_session_ = allocator_->CreateSession(name(), content_type());
+
+  uint32 flags = PORTALLOCATOR_DISABLE_UDP | PORTALLOCATOR_DISABLE_TCP;
+
+#if !defined(FEATURE_ENABLE_STUN_CLASSIFICATION)
+  flags |= PORTALLOCATOR_DISABLE_RELAY;
+#endif
+  allocator_session_->set_flags(flags);
+  allocator_session_->SignalPortReady.connect(
+      this, &RawTransportChannel::OnPortReady);
+  allocator_session_->SignalCandidatesReady.connect(
+      this, &RawTransportChannel::OnCandidatesReady);
+
+  // The initial ports will include stun.
+  allocator_session_->GetInitialPorts();
+}
+
+void RawTransportChannel::Reset() {
+  set_readable(false);
+  set_writable(false);
+
+  delete allocator_session_;
+
+  allocator_session_ = NULL;
+  stun_port_ = NULL;
+  relay_port_ = NULL;
+  port_ = NULL;
+  remote_address_ = talk_base::SocketAddress();
+}
+
+void RawTransportChannel::OnCandidate(const Candidate& candidate) {
+  remote_address_ = candidate.address();
+  ASSERT(!remote_address_.IsAny());
+  set_readable(true);
+
+  // We can write once we have a port and a remote address.
+  if (port_ != NULL)
+    SetWritable();
+}
+
+void RawTransportChannel::OnRemoteAddress(
+    const talk_base::SocketAddress& remote_address) {
+  remote_address_ = remote_address;
+  set_readable(true);
+
+  if (port_ != NULL)
+    SetWritable();
+}
+
+// Note about stun classification
+// Code to classify our NAT type and use the relay port if we are behind an
+// asymmetric NAT is under a FEATURE_ENABLE_STUN_CLASSIFICATION #define.
+// To turn this one we will have to enable a second stun address and make sure
+// that the relay server works for raw UDP.
+//
+// Another option is to classify the NAT type early and not offer the raw
+// transport type at all if we can't support it.
+
+void RawTransportChannel::OnPortReady(
+    PortAllocatorSession* session, Port* port) {
+  ASSERT(session == allocator_session_);
+
+  if (port->type() == STUN_PORT_TYPE) {
+    stun_port_ = static_cast<StunPort*>(port);
+
+#if defined(FEATURE_ENABLE_STUN_CLASSIFICATION)
+    // We need a secondary address to determine the NAT type.
+    stun_port_->PrepareSecondaryAddress();
+#endif
+  } else if (port->type() == RELAY_PORT_TYPE) {
+    relay_port_ = static_cast<RelayPort*>(port);
+  } else {
+    ASSERT(false);
+  }
+}
+
+void RawTransportChannel::OnCandidatesReady(
+    PortAllocatorSession *session, const std::vector<Candidate>& candidates) {
+  ASSERT(session == allocator_session_);
+  ASSERT(candidates.size() >= 1);
+
+  // The most recent candidate is the one we haven't seen yet.
+  Candidate c = candidates[candidates.size() - 1];
+
+  if (c.type() == STUN_PORT_TYPE) {
+    ASSERT(stun_port_ != NULL);
+
+#if defined(FEATURE_ENABLE_STUN_CLASSIFICATION)
+    // We need to wait until we have two addresses.
+    if (stun_port_->candidates().size() < 2)
+      return;
+
+    // This is the second address.  If these addresses are the same, then we
+    // are not behind a symmetric NAT.  Hence, a stun port should be sufficient.
+    if (stun_port_->candidates()[0].address() ==
+        stun_port_->candidates()[1].address()) {
+      SetPort(stun_port_);
+      return;
+    }
+
+    // We will need to use relay.
+    use_relay_ = true;
+
+    // If we weren't given a relay port, we'll need to request it.
+    if (relay_port_ == NULL) {
+      allocator_session_->StartGetAllPorts();
+      return;
+    }
+
+    // If we already have a relay address, we're good.  Otherwise, we will need
+    // to wait until one arrives.
+    if (relay_port_->candidates().size() > 0)
+      SetPort(relay_port_);
+#else  // defined(FEATURE_ENABLE_STUN_CLASSIFICATION)
+    // Always use the stun port.  We don't classify right now so just assume it
+    // will work fine.
+    SetPort(stun_port_);
+#endif
+  } else if (c.type() == RELAY_PORT_TYPE) {
+    if (use_relay_)
+      SetPort(relay_port_);
+  } else {
+    ASSERT(false);
+  }
+}
+
+void RawTransportChannel::SetPort(Port* port) {
+  ASSERT(port_ == NULL);
+  port_ = port;
+
+  // We don't need any ports other than the one we picked.
+  allocator_session_->StopGetAllPorts();
+  worker_thread_->Post(
+      this, MSG_DESTROY_UNUSED_PORTS, NULL);
+
+  // Send a message to the other client containing our address.
+
+  ASSERT(port_->candidates().size() >= 1);
+  ASSERT(port_->candidates()[0].protocol() == "udp");
+  SignalCandidateReady(this, port_->candidates()[0]);
+
+  // Read all packets from this port.
+  port_->EnablePortPackets();
+  port_->SignalReadPacket.connect(this, &RawTransportChannel::OnReadPacket);
+
+  // We can write once we have a port and a remote address.
+  if (!remote_address_.IsAny())
+    SetWritable();
+}
+
+void RawTransportChannel::SetWritable() {
+  ASSERT(port_ != NULL);
+  ASSERT(!remote_address_.IsAny());
+
+  set_writable(true);
+
+  SignalRouteChange(this, remote_address_);
+}
+
+void RawTransportChannel::OnReadPacket(
+    Port* port, const char* data, size_t size,
+    const talk_base::SocketAddress& addr) {
+  ASSERT(port_ == port);
+  SignalReadPacket(this, data, size);
+}
+
+void RawTransportChannel::OnMessage(talk_base::Message* msg) {
+  ASSERT(msg->message_id == MSG_DESTROY_UNUSED_PORTS);
+  ASSERT(port_ != NULL);
+  if (port_ != stun_port_) {
+    stun_port_->Destroy();
+    stun_port_ = NULL;
+  }
+  if (port_ != relay_port_ && relay_port_ != NULL) {
+    relay_port_->Destroy();
+    relay_port_ = NULL;
+  }
+}
+
+}  // namespace cricket
+#endif  // defined(FEATURE_ENABLE_PSTN)
diff --git a/talk/p2p/base/rawtransportchannel.h b/talk/p2p/base/rawtransportchannel.h
new file mode 100644
index 0000000..f99c9c4
--- /dev/null
+++ b/talk/p2p/base/rawtransportchannel.h
@@ -0,0 +1,131 @@
+/*
+ * 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_P2P_BASE_RAWTRANSPORTCHANNEL_H_
+#define TALK_P2P_BASE_RAWTRANSPORTCHANNEL_H_
+
+#include <string>
+#include <vector>
+#include "talk/base/messagequeue.h"
+#include "talk/p2p/base/transportchannelimpl.h"
+#include "talk/p2p/base/rawtransport.h"
+#include "talk/p2p/base/candidate.h"
+
+#if defined(FEATURE_ENABLE_PSTN)
+
+namespace talk_base {
+class Thread;
+}
+
+namespace cricket {
+
+class Port;
+class Connection;
+class StunPort;
+class RelayPort;
+class PortAllocator;
+class PortAllocatorSession;
+
+// Implements a channel that just sends bare packets once we have received the
+// address of the other side.  We pick a single address to send them based on
+// a simple investigation of NAT type.
+class RawTransportChannel : public TransportChannelImpl,
+    public talk_base::MessageHandler {
+ public:
+  RawTransportChannel(const std::string &name,
+                      const std::string &content_type,
+                      RawTransport* transport,
+                      talk_base::Thread *worker_thread,
+                      PortAllocator *allocator);
+  virtual ~RawTransportChannel();
+
+  // Implementation of normal channel packet sending.
+  virtual int SendPacket(const char *data, size_t len);
+  virtual int SetOption(talk_base::Socket::Option opt, int value);
+  virtual int GetError();
+
+  // Returns the raw transport that created this channel.
+  virtual Transport* GetTransport() { return raw_transport_; }
+
+  // Creates an allocator session to start figuring out which type of
+  // port we should send to the other client.  This will send
+  // SignalAvailableCandidate once we have decided.
+  virtual void Connect();
+
+  // Resets state back to unconnected.
+  virtual void Reset();
+
+  // We don't actually worry about signaling since we can't send new candidates.
+  virtual void OnSignalingReady() {}
+
+  // Handles a message setting the remote address.  We are writable once we
+  // have this since we now know where to send.
+  virtual void OnCandidate(const Candidate& candidate);
+
+  void OnRemoteAddress(const talk_base::SocketAddress& remote_address);
+
+
+ private:
+  RawTransport* raw_transport_;
+  talk_base::Thread *worker_thread_;
+  PortAllocator* allocator_;
+  PortAllocatorSession* allocator_session_;
+  StunPort* stun_port_;
+  RelayPort* relay_port_;
+  Port* port_;
+  bool use_relay_;
+  talk_base::SocketAddress remote_address_;
+
+  // Called when the allocator creates another port.
+  void OnPortReady(PortAllocatorSession* session, Port* port);
+
+  // Called when one of the ports we are using has determined its address.
+  void OnCandidatesReady(PortAllocatorSession *session,
+                         const std::vector<Candidate>& candidates);
+
+  // Called once we have chosen the port to use for communication with the
+  // other client.  This will send its address and prepare the port for use.
+  void SetPort(Port* port);
+
+  // Called once we have a port and a remote address.  This will set mark the
+  // channel as writable and signal the route to the client.
+  void SetWritable();
+
+  // Called when we receive a packet from the other client.
+  void OnReadPacket(Port* port, const char* data, size_t size,
+                    const talk_base::SocketAddress& addr);
+
+  // Handles a message to destroy unused ports.
+  virtual void OnMessage(talk_base::Message *msg);
+
+  DISALLOW_EVIL_CONSTRUCTORS(RawTransportChannel);
+};
+
+}  // namespace cricket
+
+#endif  // defined(FEATURE_ENABLE_PSTN)
+#endif  // TALK_P2P_BASE_RAWTRANSPORTCHANNEL_H_
diff --git a/talk/p2p/base/relayport.cc b/talk/p2p/base/relayport.cc
new file mode 100644
index 0000000..2d3be61
--- /dev/null
+++ b/talk/p2p/base/relayport.cc
@@ -0,0 +1,793 @@
+/*
+ * 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/base/asyncpacketsocket.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/p2p/base/relayport.h"
+
+namespace cricket {
+
+static const uint32 kMessageConnectTimeout = 1;
+static const int kKeepAliveDelay           = 10 * 60 * 1000;
+static const int kRetryTimeout             = 50 * 1000;  // ICE says 50 secs
+// How long to wait for a socket to connect to remote host in milliseconds
+// before trying another connection.
+static const int kSoftConnectTimeoutMs     = 3 * 1000;
+
+// Handles a connection to one address/port/protocol combination for a
+// particular RelayEntry.
+class RelayConnection : public sigslot::has_slots<> {
+ public:
+  RelayConnection(const ProtocolAddress* protocol_address,
+                  talk_base::AsyncPacketSocket* socket,
+                  talk_base::Thread* thread);
+  ~RelayConnection();
+  talk_base::AsyncPacketSocket* socket() const { return socket_; }
+
+  const ProtocolAddress* protocol_address() {
+    return protocol_address_;
+  }
+
+  talk_base::SocketAddress GetAddress() const {
+    return protocol_address_->address;
+  }
+
+  ProtocolType GetProtocol() const {
+    return protocol_address_->proto;
+  }
+
+  int SetSocketOption(talk_base::Socket::Option opt, int value);
+
+  // Validates a response to a STUN allocate request.
+  bool CheckResponse(StunMessage* msg);
+
+  // Sends data to the relay server.
+  int Send(const void* pv, size_t cb);
+
+  // Sends a STUN allocate request message to the relay server.
+  void SendAllocateRequest(RelayEntry* entry, int delay);
+
+  // Return the latest error generated by the socket.
+  int GetError() { return socket_->GetError(); }
+
+  // Called on behalf of a StunRequest to write data to the socket.  This is
+  // already STUN intended for the server, so no wrapping is necessary.
+  void OnSendPacket(const void* data, size_t size, StunRequest* req);
+
+ private:
+  talk_base::AsyncPacketSocket* socket_;
+  const ProtocolAddress* protocol_address_;
+  StunRequestManager *request_manager_;
+};
+
+// Manages a number of connections to the relayserver, one for each
+// available protocol. We aim to use each connection for only a
+// specific destination address so that we can avoid wrapping every
+// packet in a STUN send / data indication.
+class RelayEntry : public talk_base::MessageHandler,
+                   public sigslot::has_slots<> {
+ public:
+  RelayEntry(RelayPort* port, const talk_base::SocketAddress& ext_addr);
+  ~RelayEntry();
+
+  RelayPort* port() { return port_; }
+
+  const talk_base::SocketAddress& address() const { return ext_addr_; }
+  void set_address(const talk_base::SocketAddress& addr) { ext_addr_ = addr; }
+
+  bool connected() const { return connected_; }
+  bool locked() const { return locked_; }
+
+  // Returns the last error on the socket of this entry.
+  int GetError();
+
+  // Returns the most preferred connection of the given
+  // ones. Connections are rated based on protocol in the order of:
+  // UDP, TCP and SSLTCP, where UDP is the most preferred protocol
+  static RelayConnection* GetBestConnection(RelayConnection* conn1,
+                                            RelayConnection* conn2);
+
+  // Sends the STUN requests to the server to initiate this connection.
+  void Connect();
+
+  // Called when this entry becomes connected.  The address given is the one
+  // exposed to the outside world on the relay server.
+  void OnConnect(const talk_base::SocketAddress& mapped_addr,
+                 RelayConnection* socket);
+
+  // Sends a packet to the given destination address using the socket of this
+  // entry.  This will wrap the packet in STUN if necessary.
+  int SendTo(const void* data, size_t size,
+    const talk_base::SocketAddress& addr);
+
+  // Schedules a keep-alive allocate request.
+  void ScheduleKeepAlive();
+
+  void SetServerIndex(size_t sindex) { server_index_ = sindex; }
+
+  // Sets this option on the socket of each connection.
+  int SetSocketOption(talk_base::Socket::Option opt, int value);
+
+  size_t ServerIndex() const { return server_index_; }
+
+  // Try a different server address
+  void HandleConnectFailure(talk_base::AsyncPacketSocket* socket);
+
+  // Implementation of the MessageHandler Interface.
+  virtual void OnMessage(talk_base::Message *pmsg);
+
+ private:
+  RelayPort* port_;
+  talk_base::SocketAddress ext_addr_;
+  size_t server_index_;
+  bool connected_;
+  bool locked_;
+  RelayConnection* current_connection_;
+
+  // Called when a TCP connection is established or fails
+  void OnSocketConnect(talk_base::AsyncPacketSocket* socket);
+  void OnSocketClose(talk_base::AsyncPacketSocket* socket, int error);
+
+  // Called when a packet is received on this socket.
+  void OnReadPacket(talk_base::AsyncPacketSocket* socket,
+                    const char* data, size_t size,
+                    const talk_base::SocketAddress& remote_addr);
+
+  // Sends the given data on the socket to the server with no wrapping.  This
+  // returns the number of bytes written or -1 if an error occurred.
+  int SendPacket(const void* data, size_t size);
+};
+
+// Handles an allocate request for a particular RelayEntry.
+class AllocateRequest : public StunRequest {
+ public:
+  AllocateRequest(RelayEntry* entry, RelayConnection* connection);
+  virtual ~AllocateRequest() {}
+
+  virtual void Prepare(StunMessage* request);
+
+  virtual int GetNextDelay();
+
+  virtual void OnResponse(StunMessage* response);
+  virtual void OnErrorResponse(StunMessage* response);
+  virtual void OnTimeout();
+
+ private:
+  RelayEntry* entry_;
+  RelayConnection* connection_;
+  uint32 start_time_;
+};
+
+const std::string RELAY_PORT_TYPE("relay");
+
+RelayPort::RelayPort(
+    talk_base::Thread* thread, talk_base::PacketSocketFactory* factory,
+    talk_base::Network* network, uint32 ip, int min_port, int max_port,
+    const std::string& username, const std::string& password,
+    const std::string& magic_cookie)
+    : Port(thread, RELAY_PORT_TYPE, factory, network, ip, min_port, max_port),
+      ready_(false),
+      magic_cookie_(magic_cookie),
+      error_(0) {
+  entries_.push_back(
+      new RelayEntry(this, talk_base::SocketAddress()));
+
+  set_username_fragment(username);
+  set_password(password);
+  if (magic_cookie_.size() == 0)
+    magic_cookie_.append(STUN_MAGIC_COOKIE_VALUE, 4);
+}
+
+RelayPort::~RelayPort() {
+  for (size_t i = 0; i < entries_.size(); ++i)
+    delete entries_[i];
+  thread_->Clear(this);
+}
+
+void RelayPort::AddServerAddress(const ProtocolAddress& addr) {
+  // Since HTTP proxies usually only allow 443,
+  // let's up the priority on PROTO_SSLTCP
+  if (addr.proto == PROTO_SSLTCP &&
+      (proxy().type == talk_base::PROXY_HTTPS ||
+       proxy().type == talk_base::PROXY_UNKNOWN)) {
+    server_addr_.push_front(addr);
+  } else {
+    server_addr_.push_back(addr);
+  }
+}
+
+void RelayPort::AddExternalAddress(const ProtocolAddress& addr) {
+  std::string proto_name = ProtoToString(addr.proto);
+  for (std::vector<Candidate>::const_iterator it = candidates().begin();
+       it != candidates().end(); ++it) {
+    if ((it->address() == addr.address) && (it->protocol() == proto_name)) {
+      LOG(INFO) << "Redundant relay address: " << proto_name
+                << " @ " << addr.address.ToString();
+      return;
+    }
+  }
+  AddAddress(addr.address, proto_name, false);
+}
+
+void RelayPort::SetReady() {
+  if (!ready_) {
+    ready_ = true;
+    SignalAddressReady(this);
+  }
+}
+
+const ProtocolAddress * RelayPort::ServerAddress(size_t index) const {
+  if (index < server_addr_.size())
+    return &server_addr_[index];
+  return NULL;
+}
+
+bool RelayPort::HasMagicCookie(const char* data, size_t size) {
+  if (size < 24 + magic_cookie_.size()) {
+    return false;
+  } else {
+    return 0 == std::memcmp(data + 24,
+                            magic_cookie_.c_str(),
+                            magic_cookie_.size());
+  }
+}
+
+void RelayPort::PrepareAddress() {
+  // We initiate a connect on the first entry.  If this completes, it will fill
+  // in the server address as the address of this port.
+  ASSERT(entries_.size() == 1);
+  entries_[0]->Connect();
+  ready_ = false;
+}
+
+Connection* RelayPort::CreateConnection(const Candidate& address,
+                                        CandidateOrigin origin) {
+  // We only create conns to non-udp sockets if they are incoming on this port
+  if ((address.protocol() != "udp") && (origin != ORIGIN_THIS_PORT)) {
+    return 0;
+  }
+
+  // We don't support loopback on relays
+  if (address.type() == type()) {
+    return 0;
+  }
+
+  size_t index = 0;
+  for (size_t i = 0; i < candidates().size(); ++i) {
+    const Candidate& local = candidates()[i];
+    if (local.protocol() == address.protocol()) {
+      index = i;
+      break;
+    }
+  }
+
+  Connection * conn = new ProxyConnection(this, index, address);
+  AddConnection(conn);
+  return conn;
+}
+
+int RelayPort::SendTo(const void* data, size_t size,
+                      const talk_base::SocketAddress& addr, bool payload) {
+  // Try to find an entry for this specific address.  Note that the first entry
+  // created was not given an address initially, so it can be set to the first
+  // address that comes along.
+  RelayEntry* entry = 0;
+
+  for (size_t i = 0; i < entries_.size(); ++i) {
+    if (entries_[i]->address().IsAny() && payload) {
+      entry = entries_[i];
+      entry->set_address(addr);
+      break;
+    } else if (entries_[i]->address() == addr) {
+      entry = entries_[i];
+      break;
+    }
+  }
+
+  // If we did not find one, then we make a new one.  This will not be useable
+  // until it becomes connected, however.
+  if (!entry && payload) {
+    entry = new RelayEntry(this, addr);
+    if (!entries_.empty()) {
+      entry->SetServerIndex(entries_[0]->ServerIndex());
+    }
+    entry->Connect();
+    entries_.push_back(entry);
+  }
+
+  // If the entry is connected, then we can send on it (though wrapping may
+  // still be necessary).  Otherwise, we can't yet use this connection, so we
+  // default to the first one.
+  if (!entry || !entry->connected()) {
+    ASSERT(!entries_.empty());
+    entry = entries_[0];
+    if (!entry->connected()) {
+      error_ = EWOULDBLOCK;
+      return SOCKET_ERROR;
+    }
+  }
+
+  // Send the actual contents to the server using the usual mechanism.
+  int sent = entry->SendTo(data, size, addr);
+  if (sent <= 0) {
+    ASSERT(sent < 0);
+    error_ = entry->GetError();
+    return SOCKET_ERROR;
+  }
+  // The caller of the function is expecting the number of user data bytes,
+  // rather than the size of the packet.
+  return size;
+}
+
+int RelayPort::SetOption(talk_base::Socket::Option opt, int value) {
+  int result = 0;
+  for (size_t i = 0; i < entries_.size(); ++i) {
+    if (entries_[i]->SetSocketOption(opt, value) < 0) {
+      result = -1;
+      error_ = entries_[i]->GetError();
+    }
+  }
+  options_.push_back(OptionValue(opt, value));
+  return result;
+}
+
+int RelayPort::GetError() {
+  return error_;
+}
+
+void RelayPort::OnReadPacket(
+    const char* data, size_t size,
+    const talk_base::SocketAddress& remote_addr) {
+  if (Connection* conn = GetConnection(remote_addr)) {
+    conn->OnReadPacket(data, size);
+  } else {
+    Port::OnReadPacket(data, size, remote_addr);
+  }
+}
+
+RelayConnection::RelayConnection(const ProtocolAddress* protocol_address,
+                                 talk_base::AsyncPacketSocket* socket,
+                                 talk_base::Thread* thread)
+    : socket_(socket),
+      protocol_address_(protocol_address) {
+  request_manager_ = new StunRequestManager(thread);
+  request_manager_->SignalSendPacket.connect(this,
+                                             &RelayConnection::OnSendPacket);
+}
+
+RelayConnection::~RelayConnection() {
+  delete request_manager_;
+  delete socket_;
+}
+
+int RelayConnection::SetSocketOption(talk_base::Socket::Option opt,
+                                     int value) {
+  if (socket_) {
+    return socket_->SetOption(opt, value);
+  }
+  return 0;
+}
+
+bool RelayConnection::CheckResponse(StunMessage* msg) {
+  return request_manager_->CheckResponse(msg);
+}
+
+void RelayConnection::OnSendPacket(const void* data, size_t size,
+                                   StunRequest* req) {
+  int sent = socket_->SendTo(data, size, GetAddress());
+  if (sent <= 0) {
+    LOG(LS_VERBOSE) << "OnSendPacket: failed sending to " << GetAddress() <<
+        std::strerror(socket_->GetError());
+    ASSERT(sent < 0);
+  }
+}
+
+int RelayConnection::Send(const void* pv, size_t cb) {
+  return socket_->SendTo(pv, cb, GetAddress());
+}
+
+void RelayConnection::SendAllocateRequest(RelayEntry* entry, int delay) {
+  request_manager_->SendDelayed(new AllocateRequest(entry, this), delay);
+}
+
+RelayEntry::RelayEntry(RelayPort* port,
+                       const talk_base::SocketAddress& ext_addr)
+    : port_(port), ext_addr_(ext_addr),
+      server_index_(0), connected_(false), locked_(false),
+      current_connection_(NULL) {
+}
+
+RelayEntry::~RelayEntry() {
+  // Remove all RelayConnections and dispose sockets.
+  delete current_connection_;
+  current_connection_ = NULL;
+}
+
+void RelayEntry::Connect() {
+  // If we're already connected, return.
+  if (connected_)
+    return;
+
+  // If we've exhausted all options, bail out.
+  const ProtocolAddress* ra = port()->ServerAddress(server_index_);
+  if (!ra) {
+    LOG(LS_WARNING) << "No more relay addresses left to try";
+    return;
+  }
+
+  // Remove any previous connection.
+  if (current_connection_) {
+    port()->thread()->Dispose(current_connection_);
+    current_connection_ = NULL;
+  }
+
+  // Try to set up our new socket.
+  LOG(LS_INFO) << "Connecting to relay via " << ProtoToString(ra->proto) <<
+      " @ " << ra->address.ToString();
+
+  talk_base::AsyncPacketSocket* socket = NULL;
+
+  if (ra->proto == PROTO_UDP) {
+    // UDP sockets are simple.
+    socket = port_->socket_factory()->CreateUdpSocket(
+        talk_base::SocketAddress(port_->ip_, 0),
+        port_->min_port_, port_->max_port_);
+  } else if (ra->proto == PROTO_TCP || ra->proto == PROTO_SSLTCP) {
+    socket = port_->socket_factory()->CreateClientTcpSocket(
+        talk_base::SocketAddress(port_->ip_, 0), ra->address,
+        port_->proxy(), port_->user_agent(), ra->proto == PROTO_SSLTCP);
+  } else {
+    LOG(LS_WARNING) << "Unknown protocol (" << ra->proto << ")";
+  }
+
+  if (!socket) {
+    LOG(LS_WARNING) << "Socket creation failed";
+  }
+
+  // If we failed to get a socket, move on to the next protocol.
+  if (!socket) {
+    port()->thread()->Post(this, kMessageConnectTimeout);
+    return;
+  }
+
+  // Otherwise, create the new connection and configure any socket options.
+  socket->SignalReadPacket.connect(this, &RelayEntry::OnReadPacket);
+  current_connection_ = new RelayConnection(ra, socket, port()->thread());
+  for (size_t i = 0; i < port_->options().size(); ++i) {
+    current_connection_->SetSocketOption(port_->options()[i].first,
+                                         port_->options()[i].second);
+  }
+
+  // If we're trying UDP, start binding requests.
+  // If we're trying TCP, wait for connection with a fixed timeout.
+  if ((ra->proto == PROTO_TCP) || (ra->proto == PROTO_SSLTCP)) {
+    socket->SignalClose.connect(this, &RelayEntry::OnSocketClose);
+    socket->SignalConnect.connect(this, &RelayEntry::OnSocketConnect);
+    port()->thread()->PostDelayed(kSoftConnectTimeoutMs, this,
+                                  kMessageConnectTimeout);
+  } else {
+    current_connection_->SendAllocateRequest(this, 0);
+  }
+}
+
+int RelayEntry::GetError() {
+  if (current_connection_ != NULL) {
+    return current_connection_->GetError();
+  }
+  return 0;
+}
+
+RelayConnection* RelayEntry::GetBestConnection(RelayConnection* conn1,
+                                               RelayConnection* conn2) {
+  return conn1->GetProtocol() <= conn2->GetProtocol() ? conn1 : conn2;
+}
+
+void RelayEntry::OnConnect(const talk_base::SocketAddress& mapped_addr,
+                           RelayConnection* connection) {
+  // We are connected, notify our parent.
+  ProtocolType proto = PROTO_UDP;
+  LOG(INFO) << "Relay allocate succeeded: " << ProtoToString(proto)
+            << " @ " << mapped_addr.ToString();
+  connected_ = true;
+
+  port_->AddExternalAddress(ProtocolAddress(mapped_addr, proto));
+  port_->SetReady();
+}
+
+int RelayEntry::SendTo(const void* data, size_t size,
+                       const talk_base::SocketAddress& addr) {
+  // If this connection is locked to the address given, then we can send the
+  // packet with no wrapper.
+  if (locked_ && (ext_addr_ == addr))
+    return SendPacket(data, size);
+
+  // Otherwise, we must wrap the given data in a STUN SEND request so that we
+  // can communicate the destination address to the server.
+  //
+  // Note that we do not use a StunRequest here.  This is because there is
+  // likely no reason to resend this packet. If it is late, we just drop it.
+  // The next send to this address will try again.
+
+  StunMessage request;
+  request.SetType(STUN_SEND_REQUEST);
+  request.SetTransactionID(talk_base::CreateRandomString(16));
+
+  StunByteStringAttribute* magic_cookie_attr =
+      StunAttribute::CreateByteString(STUN_ATTR_MAGIC_COOKIE);
+  magic_cookie_attr->CopyBytes(port_->magic_cookie().c_str(),
+                               port_->magic_cookie().size());
+  request.AddAttribute(magic_cookie_attr);
+
+  StunByteStringAttribute* username_attr =
+      StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
+  username_attr->CopyBytes(port_->username_fragment().c_str(),
+                           port_->username_fragment().size());
+  request.AddAttribute(username_attr);
+
+  StunAddressAttribute* addr_attr =
+      StunAttribute::CreateAddress(STUN_ATTR_DESTINATION_ADDRESS);
+  addr_attr->SetFamily(1);
+  addr_attr->SetIP(addr.ip());
+  addr_attr->SetPort(addr.port());
+  request.AddAttribute(addr_attr);
+
+  // Attempt to lock
+  if (ext_addr_ == addr) {
+    StunUInt32Attribute* options_attr =
+      StunAttribute::CreateUInt32(STUN_ATTR_OPTIONS);
+    options_attr->SetValue(0x1);
+    request.AddAttribute(options_attr);
+  }
+
+  StunByteStringAttribute* data_attr =
+      StunAttribute::CreateByteString(STUN_ATTR_DATA);
+  data_attr->CopyBytes(data, size);
+  request.AddAttribute(data_attr);
+
+  // TODO: compute the HMAC.
+
+  talk_base::ByteBuffer buf;
+  request.Write(&buf);
+
+  return SendPacket(buf.Data(), buf.Length());
+}
+
+void RelayEntry::ScheduleKeepAlive() {
+  if (current_connection_) {
+    current_connection_->SendAllocateRequest(this, kKeepAliveDelay);
+  }
+}
+
+int RelayEntry::SetSocketOption(talk_base::Socket::Option opt, int value) {
+  // Set the option on all available sockets.
+  int socket_error = 0;
+  if (current_connection_) {
+    socket_error = current_connection_->SetSocketOption(opt, value);
+  }
+  return socket_error;
+}
+
+void RelayEntry::HandleConnectFailure(
+    talk_base::AsyncPacketSocket* socket) {
+  // Make sure it's the current connection that has failed, it might
+  // be an old socked that has not yet been disposed.
+  if (!socket || socket == current_connection_->socket()) {
+    if (current_connection_)
+      port()->SignalConnectFailure(current_connection_->protocol_address());
+
+    // Try to connect to the next server address.
+    server_index_ += 1;
+    Connect();
+  }
+}
+
+void RelayEntry::OnMessage(talk_base::Message *pmsg) {
+  ASSERT(pmsg->message_id == kMessageConnectTimeout);
+  if (current_connection_) {
+    const ProtocolAddress* ra = current_connection_->protocol_address();
+    LOG(LS_WARNING) << "Relay " << ra->proto << " connection to " <<
+        ra->address << " timed out";
+
+    // Currently we connect to each server address in sequence. If we
+    // have more addresses to try, treat this is an error and move on to
+    // the next address, otherwise give this connection more time and
+    // await the real timeout.
+    //
+    // TODO: Connect to servers in parallel to speed up connect time
+    // and to avoid giving up too early.
+    port_->SignalSoftTimeout(ra);
+    HandleConnectFailure(current_connection_->socket());
+  } else {
+    HandleConnectFailure(NULL);
+  }
+}
+
+void RelayEntry::OnSocketConnect(talk_base::AsyncPacketSocket* socket) {
+  LOG(INFO) << "relay tcp connected to " <<
+      socket->GetRemoteAddress().ToString();
+  if (current_connection_ != NULL) {
+    current_connection_->SendAllocateRequest(this, 0);
+  }
+}
+
+void RelayEntry::OnSocketClose(talk_base::AsyncPacketSocket* socket,
+                               int error) {
+  PLOG(LERROR, error) << "Relay connection failed: socket closed";
+  HandleConnectFailure(socket);
+}
+
+void RelayEntry::OnReadPacket(talk_base::AsyncPacketSocket* socket,
+                              const char* data, size_t size,
+                              const talk_base::SocketAddress& remote_addr) {
+  // ASSERT(remote_addr == port_->server_addr());
+  // TODO: are we worried about this?
+
+  if (current_connection_ == NULL || socket != current_connection_->socket()) {
+    // This packet comes from an unknown address.
+    LOG(WARNING) << "Dropping packet: unknown address";
+    return;
+  }
+
+  // If the magic cookie is not present, then this is an unwrapped packet sent
+  // by the server,  The actual remote address is the one we recorded.
+  if (!port_->HasMagicCookie(data, size)) {
+    if (locked_) {
+      port_->OnReadPacket(data, size, ext_addr_);
+    } else {
+      LOG(WARNING) << "Dropping packet: entry not locked";
+    }
+    return;
+  }
+
+  talk_base::ByteBuffer buf(data, size);
+  StunMessage msg;
+  if (!msg.Read(&buf)) {
+    LOG(INFO) << "Incoming packet was not STUN";
+    return;
+  }
+
+  // The incoming packet should be a STUN ALLOCATE response, SEND response, or
+  // DATA indication.
+  if (current_connection_->CheckResponse(&msg)) {
+    return;
+  } else if (msg.type() == STUN_SEND_RESPONSE) {
+    if (const StunUInt32Attribute* options_attr =
+        msg.GetUInt32(STUN_ATTR_OPTIONS)) {
+      if (options_attr->value() & 0x1) {
+        locked_ = true;
+      }
+    }
+    return;
+  } else if (msg.type() != STUN_DATA_INDICATION) {
+    LOG(INFO) << "Received BAD stun type from server: " << msg.type();
+    return;
+  }
+
+  // This must be a data indication.
+
+  const StunAddressAttribute* addr_attr =
+      msg.GetAddress(STUN_ATTR_SOURCE_ADDRESS2);
+  if (!addr_attr) {
+    LOG(INFO) << "Data indication has no source address";
+    return;
+  } else if (addr_attr->family() != 1) {
+    LOG(INFO) << "Source address has bad family";
+    return;
+  }
+
+  talk_base::SocketAddress remote_addr2(addr_attr->ip(), addr_attr->port());
+
+  const StunByteStringAttribute* data_attr = msg.GetByteString(STUN_ATTR_DATA);
+  if (!data_attr) {
+    LOG(INFO) << "Data indication has no data";
+    return;
+  }
+
+  // Process the actual data and remote address in the normal manner.
+  port_->OnReadPacket(data_attr->bytes(), data_attr->length(), remote_addr2);
+}
+
+int RelayEntry::SendPacket(const void* data, size_t size) {
+  int sent = 0;
+  if (current_connection_) {
+    // We are connected, no need to send packets anywere else than to
+    // the current connection.
+    sent = current_connection_->Send(data, size);
+  }
+  return sent;
+}
+
+AllocateRequest::AllocateRequest(RelayEntry* entry,
+                                 RelayConnection* connection) :
+    entry_(entry), connection_(connection) {
+  start_time_ = talk_base::Time();
+}
+
+void AllocateRequest::Prepare(StunMessage* request) {
+  request->SetType(STUN_ALLOCATE_REQUEST);
+
+  StunByteStringAttribute* magic_cookie_attr =
+      StunAttribute::CreateByteString(STUN_ATTR_MAGIC_COOKIE);
+  magic_cookie_attr->CopyBytes(
+      entry_->port()->magic_cookie().c_str(),
+      entry_->port()->magic_cookie().size());
+  request->AddAttribute(magic_cookie_attr);
+
+  StunByteStringAttribute* username_attr =
+      StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
+  username_attr->CopyBytes(
+      entry_->port()->username_fragment().c_str(),
+      entry_->port()->username_fragment().size());
+  request->AddAttribute(username_attr);
+}
+
+int AllocateRequest::GetNextDelay() {
+  int delay = 100 * talk_base::_max(1 << count_, 2);
+  count_ += 1;
+  if (count_ == 5)
+    timeout_ = true;
+  return delay;
+}
+
+void AllocateRequest::OnResponse(StunMessage* response) {
+  const StunAddressAttribute* addr_attr =
+      response->GetAddress(STUN_ATTR_MAPPED_ADDRESS);
+  if (!addr_attr) {
+    LOG(INFO) << "Allocate response missing mapped address.";
+  } else if (addr_attr->family() != 1) {
+    LOG(INFO) << "Mapped address has bad family";
+  } else {
+    talk_base::SocketAddress addr(addr_attr->ip(), addr_attr->port());
+    entry_->OnConnect(addr, connection_);
+  }
+
+  // We will do a keep-alive regardless of whether this request suceeds.
+  // This should have almost no impact on network usage.
+  entry_->ScheduleKeepAlive();
+}
+
+void AllocateRequest::OnErrorResponse(StunMessage* response) {
+  const StunErrorCodeAttribute* attr = response->GetErrorCode();
+  if (!attr) {
+    LOG(INFO) << "Bad allocate response error code";
+  } else {
+    LOG(INFO) << "Allocate error response:"
+              << " code=" << static_cast<int>(attr->error_code())
+              << " reason='" << attr->reason() << "'";
+  }
+
+  if (talk_base::TimeSince(start_time_) <= kRetryTimeout)
+    entry_->ScheduleKeepAlive();
+}
+
+void AllocateRequest::OnTimeout() {
+  LOG(INFO) << "Allocate request timed out";
+  entry_->HandleConnectFailure(connection_->socket());
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/relayport.h b/talk/p2p/base/relayport.h
new file mode 100644
index 0000000..025668a
--- /dev/null
+++ b/talk/p2p/base/relayport.h
@@ -0,0 +1,115 @@
+/*
+ * 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_P2P_BASE_RELAYPORT_H_
+#define TALK_P2P_BASE_RELAYPORT_H_
+
+#include <deque>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "talk/p2p/base/port.h"
+#include "talk/p2p/base/stunrequest.h"
+
+namespace cricket {
+
+extern const std::string RELAY_PORT_TYPE;
+class RelayEntry;
+class RelayConnection;
+
+// Communicates using an allocated port on the relay server. For each
+// remote candidate that we try to send data to a RelayEntry instance
+// is created. The RelayEntry will try to reach the remote destination
+// by connecting to all available server addresses in a pre defined
+// order with a small delay in between. When a connection is
+// successful all other connection attemts are aborted.
+class RelayPort : public Port {
+ public:
+  typedef std::pair<talk_base::Socket::Option, int> OptionValue;
+
+  // RelayPort doesn't yet do anything fancy in the ctor.
+  static RelayPort* Create(
+      talk_base::Thread* thread, talk_base::PacketSocketFactory* factory,
+      talk_base::Network* network, uint32 ip, int min_port, int max_port,
+      const std::string& username, const std::string& password,
+      const std::string& magic_cookie) {
+    return new RelayPort(thread, factory, network, ip, min_port, max_port,
+                         username, password, magic_cookie);
+  }
+  virtual ~RelayPort();
+
+  void AddServerAddress(const ProtocolAddress& addr);
+  void AddExternalAddress(const ProtocolAddress& addr);
+
+  const std::vector<OptionValue>& options() const { return options_; }
+  const std::string& magic_cookie() const { return magic_cookie_; }
+  bool HasMagicCookie(const char* data, size_t size);
+
+  virtual void PrepareAddress();
+  virtual Connection* CreateConnection(const Candidate& address,
+                                       CandidateOrigin origin);
+  virtual int SetOption(talk_base::Socket::Option opt, int value);
+  virtual int GetError();
+
+  const ProtocolAddress * ServerAddress(size_t index) const;
+  bool IsReady() { return ready_; }
+
+  // Used for testing.
+  sigslot::signal1<const ProtocolAddress*> SignalConnectFailure;
+  sigslot::signal1<const ProtocolAddress*> SignalSoftTimeout;
+
+ protected:
+  RelayPort(talk_base::Thread* thread, talk_base::PacketSocketFactory* factory,
+            talk_base::Network*, uint32 ip, int min_port, int max_port,
+            const std::string& username, const std::string& password,
+            const std::string& magic_cookie);
+  bool Init();
+
+  void SetReady();
+
+  virtual int SendTo(const void* data, size_t size,
+                     const talk_base::SocketAddress& addr, bool payload);
+
+  // Dispatches the given packet to the port or connection as appropriate.
+  void OnReadPacket(const char* data, size_t size,
+                    const talk_base::SocketAddress& remote_addr);
+
+ private:
+  friend class RelayEntry;
+
+  std::deque<ProtocolAddress> server_addr_;
+  bool ready_;
+  std::vector<RelayEntry*> entries_;
+  std::vector<OptionValue> options_;
+  std::string magic_cookie_;
+  int error_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_RELAYPORT_H_
diff --git a/talk/p2p/base/relayserver.cc b/talk/p2p/base/relayserver.cc
new file mode 100644
index 0000000..111b340
--- /dev/null
+++ b/talk/p2p/base/relayserver.cc
@@ -0,0 +1,769 @@
+/*
+ * 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/p2p/base/relayserver.h"
+
+#ifdef POSIX
+#include <errno.h>
+#endif  // POSIX
+
+#include <algorithm>
+
+#include "talk/base/asynctcpsocket.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/socketadapters.h"
+
+namespace cricket {
+
+// By default, we require a ping every 90 seconds.
+const int MAX_LIFETIME = 15 * 60 * 1000;
+
+// The number of bytes in each of the usernames we use.
+const uint32 USERNAME_LENGTH = 16;
+
+static const uint32 kMessageAcceptConnection = 1;
+
+// Calls SendTo on the given socket and logs any bad results.
+void Send(talk_base::AsyncPacketSocket* socket, const char* bytes, size_t size,
+          const talk_base::SocketAddress& addr) {
+  int result = socket->SendTo(bytes, size, addr);
+  if (result < static_cast<int>(size)) {
+    LOG(LS_ERROR) << "SendTo wrote only " << result << " of " << size
+                  << " bytes";
+  } else if (result < 0) {
+    LOG_ERR(LS_ERROR) << "SendTo";
+  }
+}
+
+// Sends the given STUN message on the given socket.
+void SendStun(const StunMessage& msg,
+              talk_base::AsyncPacketSocket* socket,
+              const talk_base::SocketAddress& addr) {
+  talk_base::ByteBuffer buf;
+  msg.Write(&buf);
+  Send(socket, buf.Data(), buf.Length(), addr);
+}
+
+// Constructs a STUN error response and sends it on the given socket.
+void SendStunError(const StunMessage& msg, talk_base::AsyncPacketSocket* socket,
+                   const talk_base::SocketAddress& remote_addr, int error_code,
+                   const char* error_desc, const std::string& magic_cookie) {
+  StunMessage err_msg;
+  err_msg.SetType(GetStunErrorResponseType(msg.type()));
+  err_msg.SetTransactionID(msg.transaction_id());
+
+  StunByteStringAttribute* magic_cookie_attr =
+      StunAttribute::CreateByteString(cricket::STUN_ATTR_MAGIC_COOKIE);
+  if (magic_cookie.size() == 0)
+    magic_cookie_attr->CopyBytes(cricket::STUN_MAGIC_COOKIE_VALUE, 4);
+  else
+    magic_cookie_attr->CopyBytes(magic_cookie.c_str(), magic_cookie.size());
+  err_msg.AddAttribute(magic_cookie_attr);
+
+  StunErrorCodeAttribute* err_code = StunAttribute::CreateErrorCode();
+  err_code->SetErrorClass(error_code / 100);
+  err_code->SetNumber(error_code % 100);
+  err_code->SetReason(error_desc);
+  err_msg.AddAttribute(err_code);
+
+  SendStun(err_msg, socket, remote_addr);
+}
+
+RelayServer::RelayServer(talk_base::Thread* thread)
+  : thread_(thread), log_bindings_(true) {
+}
+
+RelayServer::~RelayServer() {
+  // Deleting the binding will cause it to be removed from the map.
+  while (!bindings_.empty())
+    delete bindings_.begin()->second;
+  for (size_t i = 0; i < internal_sockets_.size(); ++i)
+    delete internal_sockets_[i];
+  for (size_t i = 0; i < external_sockets_.size(); ++i)
+    delete external_sockets_[i];
+  while (!server_sockets_.empty()) {
+    talk_base::AsyncSocket* socket = server_sockets_.begin()->first;
+    server_sockets_.erase(server_sockets_.begin()->first);
+    delete socket;
+  }
+}
+
+void RelayServer::AddInternalSocket(talk_base::AsyncPacketSocket* socket) {
+  ASSERT(internal_sockets_.end() ==
+      std::find(internal_sockets_.begin(), internal_sockets_.end(), socket));
+  internal_sockets_.push_back(socket);
+  socket->SignalReadPacket.connect(this, &RelayServer::OnInternalPacket);
+}
+
+void RelayServer::RemoveInternalSocket(talk_base::AsyncPacketSocket* socket) {
+  SocketList::iterator iter =
+      std::find(internal_sockets_.begin(), internal_sockets_.end(), socket);
+  ASSERT(iter != internal_sockets_.end());
+  internal_sockets_.erase(iter);
+  socket->SignalReadPacket.disconnect(this);
+}
+
+void RelayServer::AddExternalSocket(talk_base::AsyncPacketSocket* socket) {
+  ASSERT(external_sockets_.end() ==
+      std::find(external_sockets_.begin(), external_sockets_.end(), socket));
+  external_sockets_.push_back(socket);
+  socket->SignalReadPacket.connect(this, &RelayServer::OnExternalPacket);
+}
+
+void RelayServer::RemoveExternalSocket(talk_base::AsyncPacketSocket* socket) {
+  SocketList::iterator iter =
+      std::find(external_sockets_.begin(), external_sockets_.end(), socket);
+  ASSERT(iter != external_sockets_.end());
+  external_sockets_.erase(iter);
+  socket->SignalReadPacket.disconnect(this);
+}
+
+void RelayServer::AddInternalServerSocket(talk_base::AsyncSocket* socket,
+                                          cricket::ProtocolType proto) {
+  ASSERT(server_sockets_.end() ==
+         server_sockets_.find(socket));
+  server_sockets_[socket] = proto;
+  socket->SignalReadEvent.connect(this, &RelayServer::OnReadEvent);
+}
+
+void RelayServer::RemoveInternalServerSocket(
+    talk_base::AsyncSocket* socket) {
+  ServerSocketMap::iterator iter = server_sockets_.find(socket);
+  ASSERT(iter != server_sockets_.end());
+  server_sockets_.erase(iter);
+  socket->SignalReadEvent.disconnect(this);
+}
+
+int RelayServer::GetConnectionCount() const {
+  return connections_.size();
+}
+
+talk_base::SocketAddressPair RelayServer::GetConnection(int connection) const {
+  int i = 0;
+  for (ConnectionMap::const_iterator it = connections_.begin();
+       it != connections_.end(); ++it) {
+    if (i == connection) {
+      return it->second->addr_pair();
+    }
+    ++i;
+  }
+  return talk_base::SocketAddressPair();
+}
+
+bool RelayServer::HasConnection(const talk_base::SocketAddress& address) const {
+  for (ConnectionMap::const_iterator it = connections_.begin();
+       it != connections_.end(); ++it) {
+    if (it->second->addr_pair().destination() == address) {
+      return true;
+    }
+  }
+  return false;
+}
+
+void RelayServer::OnReadEvent(talk_base::AsyncSocket* socket) {
+  ServerSocketMap::iterator iter = server_sockets_.find(socket);
+  ASSERT(iter != server_sockets_.end());
+  AcceptConnection(socket);
+}
+
+void RelayServer::OnInternalPacket(
+    talk_base::AsyncPacketSocket* socket, const char* bytes, size_t size,
+    const talk_base::SocketAddress& remote_addr) {
+
+  // Get the address of the connection we just received on.
+  bool allocated;
+  talk_base::SocketAddressPair ap(
+      remote_addr, socket->GetLocalAddress(&allocated));
+  ASSERT(allocated);
+  ASSERT(!ap.destination().IsAny());
+
+  // If this did not come from an existing connection, it should be a STUN
+  // allocate request.
+  ConnectionMap::iterator piter = connections_.find(ap);
+  if (piter == connections_.end()) {
+    HandleStunAllocate(bytes, size, ap, socket);
+    return;
+  }
+
+  RelayServerConnection* int_conn = piter->second;
+
+  // Handle STUN requests to the server itself.
+  if (int_conn->binding()->HasMagicCookie(bytes, size)) {
+    HandleStun(int_conn, bytes, size);
+    return;
+  }
+
+  // Otherwise, this is a non-wrapped packet that we are to forward.  Make sure
+  // that this connection has been locked.  (Otherwise, we would not know what
+  // address to forward to.)
+  if (!int_conn->locked()) {
+    LOG(LS_WARNING) << "Dropping packet: connection not locked";
+    return;
+  }
+
+  // Forward this to the destination address into the connection.
+  RelayServerConnection* ext_conn = int_conn->binding()->GetExternalConnection(
+      int_conn->default_destination());
+  if (ext_conn && ext_conn->locked()) {
+    // TODO: Check the HMAC.
+    ext_conn->Send(bytes, size);
+  } else {
+    // This happens very often and is not an error.
+    LOG(LS_INFO) << "Dropping packet: no external connection";
+  }
+}
+
+void RelayServer::OnExternalPacket(
+    talk_base::AsyncPacketSocket* socket, const char* bytes, size_t size,
+    const talk_base::SocketAddress& remote_addr) {
+
+  // Get the address of the connection we just received on.
+  bool allocated;
+  talk_base::SocketAddressPair ap(
+      remote_addr, socket->GetLocalAddress(&allocated));
+  ASSERT(allocated);
+  ASSERT(!ap.destination().IsAny());
+
+  // If this connection already exists, then forward the traffic.
+  ConnectionMap::iterator piter = connections_.find(ap);
+  if (piter != connections_.end()) {
+    // TODO: Check the HMAC.
+    RelayServerConnection* ext_conn = piter->second;
+    RelayServerConnection* int_conn =
+        ext_conn->binding()->GetInternalConnection(
+            ext_conn->addr_pair().source());
+    ASSERT(int_conn != NULL);
+    int_conn->Send(bytes, size, ext_conn->addr_pair().source());
+    ext_conn->Lock();  // allow outgoing packets
+    return;
+  }
+
+  // The first packet should always be a STUN / TURN packet.  If it isn't, then
+  // we should just ignore this packet.
+  StunMessage msg;
+  talk_base::ByteBuffer buf(bytes, size);
+  if (!msg.Read(&buf)) {
+    LOG(LS_WARNING) << "Dropping packet: first packet not STUN";
+    return;
+  }
+
+  // The initial packet should have a username (which identifies the binding).
+  const StunByteStringAttribute* username_attr =
+      msg.GetByteString(STUN_ATTR_USERNAME);
+  if (!username_attr) {
+    LOG(LS_WARNING) << "Dropping packet: no username";
+    return;
+  }
+
+  uint32 length = talk_base::_min(static_cast<uint32>(username_attr->length()),
+                                  USERNAME_LENGTH);
+  std::string username(username_attr->bytes(), length);
+  // TODO: Check the HMAC.
+
+  // The binding should already be present.
+  BindingMap::iterator biter = bindings_.find(username);
+  if (biter == bindings_.end()) {
+    LOG(LS_WARNING) << "Dropping packet: no binding with username";
+    return;
+  }
+
+  // Add this authenticted connection to the binding.
+  RelayServerConnection* ext_conn =
+      new RelayServerConnection(biter->second, ap, socket);
+  ext_conn->binding()->AddExternalConnection(ext_conn);
+  AddConnection(ext_conn);
+
+  // We always know where external packets should be forwarded, so we can lock
+  // them from the beginning.
+  ext_conn->Lock();
+
+  // Send this message on the appropriate internal connection.
+  RelayServerConnection* int_conn = ext_conn->binding()->GetInternalConnection(
+      ext_conn->addr_pair().source());
+  ASSERT(int_conn != NULL);
+  int_conn->Send(bytes, size, ext_conn->addr_pair().source());
+}
+
+bool RelayServer::HandleStun(
+    const char* bytes, size_t size, const talk_base::SocketAddress& remote_addr,
+    talk_base::AsyncPacketSocket* socket, std::string* username,
+    StunMessage* msg) {
+
+  // Parse this into a stun message.
+  talk_base::ByteBuffer buf(bytes, size);
+  if (!msg->Read(&buf)) {
+    SendStunError(*msg, socket, remote_addr, 400, "Bad Request", "");
+    return false;
+  }
+
+  // The initial packet should have a username (which identifies the binding).
+  const StunByteStringAttribute* username_attr =
+      msg->GetByteString(STUN_ATTR_USERNAME);
+  if (!username_attr) {
+    SendStunError(*msg, socket, remote_addr, 432, "Missing Username", "");
+    return false;
+  }
+
+  // Record the username if requested.
+  if (username)
+    username->append(username_attr->bytes(), username_attr->length());
+
+  // TODO: Check for unknown attributes (<= 0x7fff)
+
+  return true;
+}
+
+void RelayServer::HandleStunAllocate(
+    const char* bytes, size_t size, const talk_base::SocketAddressPair& ap,
+    talk_base::AsyncPacketSocket* socket) {
+
+  // Make sure this is a valid STUN request.
+  StunMessage request;
+  std::string username;
+  if (!HandleStun(bytes, size, ap.source(), socket, &username, &request))
+    return;
+
+  // Make sure this is a an allocate request.
+  if (request.type() != STUN_ALLOCATE_REQUEST) {
+    SendStunError(request,
+                  socket,
+                  ap.source(),
+                  600,
+                  "Operation Not Supported",
+                  "");
+    return;
+  }
+
+  // TODO: Check the HMAC.
+
+  // Find or create the binding for this username.
+
+  RelayServerBinding* binding;
+
+  BindingMap::iterator biter = bindings_.find(username);
+  if (biter != bindings_.end()) {
+    binding = biter->second;
+  } else {
+    // NOTE: In the future, bindings will be created by the bot only.  This
+    //       else-branch will then disappear.
+
+    // Compute the appropriate lifetime for this binding.
+    uint32 lifetime = MAX_LIFETIME;
+    const StunUInt32Attribute* lifetime_attr =
+        request.GetUInt32(STUN_ATTR_LIFETIME);
+    if (lifetime_attr)
+      lifetime = talk_base::_min(lifetime, lifetime_attr->value() * 1000);
+
+    binding = new RelayServerBinding(this, username, "0", lifetime);
+    binding->SignalTimeout.connect(this, &RelayServer::OnTimeout);
+    bindings_[username] = binding;
+
+    if (log_bindings_) {
+      LOG(LS_INFO) << "Added new binding " << username << ", "
+                   << bindings_.size() << " total";
+    }
+  }
+
+  // Add this connection to the binding.  It starts out unlocked.
+  RelayServerConnection* int_conn =
+      new RelayServerConnection(binding, ap, socket);
+  binding->AddInternalConnection(int_conn);
+  AddConnection(int_conn);
+
+  // Now that we have a connection, this other method takes over.
+  HandleStunAllocate(int_conn, request);
+}
+
+void RelayServer::HandleStun(
+    RelayServerConnection* int_conn, const char* bytes, size_t size) {
+
+  // Make sure this is a valid STUN request.
+  StunMessage request;
+  std::string username;
+  if (!HandleStun(bytes, size, int_conn->addr_pair().source(),
+                  int_conn->socket(), &username, &request))
+    return;
+
+  // Make sure the username is the one were were expecting.
+  if (username != int_conn->binding()->username()) {
+    int_conn->SendStunError(request, 430, "Stale Credentials");
+    return;
+  }
+
+  // TODO: Check the HMAC.
+
+  // Send this request to the appropriate handler.
+  if (request.type() == STUN_SEND_REQUEST)
+    HandleStunSend(int_conn, request);
+  else if (request.type() == STUN_ALLOCATE_REQUEST)
+    HandleStunAllocate(int_conn, request);
+  else
+    int_conn->SendStunError(request, 600, "Operation Not Supported");
+}
+
+void RelayServer::HandleStunAllocate(
+    RelayServerConnection* int_conn, const StunMessage& request) {
+
+  // Create a response message that includes an address with which external
+  // clients can communicate.
+
+  StunMessage response;
+  response.SetType(STUN_ALLOCATE_RESPONSE);
+  response.SetTransactionID(request.transaction_id());
+
+  StunByteStringAttribute* magic_cookie_attr =
+      StunAttribute::CreateByteString(cricket::STUN_ATTR_MAGIC_COOKIE);
+  magic_cookie_attr->CopyBytes(int_conn->binding()->magic_cookie().c_str(),
+                               int_conn->binding()->magic_cookie().size());
+  response.AddAttribute(magic_cookie_attr);
+
+  size_t index = rand() % external_sockets_.size();
+  bool allocated;
+  talk_base::SocketAddress ext_addr =
+      external_sockets_[index]->GetLocalAddress(&allocated);
+  ASSERT(allocated);
+
+  StunAddressAttribute* addr_attr =
+      StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS);
+  addr_attr->SetFamily(1);
+  addr_attr->SetIP(ext_addr.ip());
+  addr_attr->SetPort(ext_addr.port());
+  response.AddAttribute(addr_attr);
+
+  StunUInt32Attribute* res_lifetime_attr =
+      StunAttribute::CreateUInt32(STUN_ATTR_LIFETIME);
+  res_lifetime_attr->SetValue(int_conn->binding()->lifetime() / 1000);
+  response.AddAttribute(res_lifetime_attr);
+
+  // TODO: Support transport-prefs (preallocate RTCP port).
+  // TODO: Support bandwidth restrictions.
+  // TODO: Add message integrity check.
+
+  // Send a response to the caller.
+  int_conn->SendStun(response);
+}
+
+void RelayServer::HandleStunSend(
+    RelayServerConnection* int_conn, const StunMessage& request) {
+
+  const StunAddressAttribute* addr_attr =
+      request.GetAddress(STUN_ATTR_DESTINATION_ADDRESS);
+  if (!addr_attr) {
+    int_conn->SendStunError(request, 400, "Bad Request");
+    return;
+  }
+
+  const StunByteStringAttribute* data_attr =
+      request.GetByteString(STUN_ATTR_DATA);
+  if (!data_attr) {
+    int_conn->SendStunError(request, 400, "Bad Request");
+    return;
+  }
+
+  talk_base::SocketAddress ext_addr(addr_attr->ip(), addr_attr->port());
+  RelayServerConnection* ext_conn =
+      int_conn->binding()->GetExternalConnection(ext_addr);
+  if (!ext_conn) {
+    // Create a new connection to establish the relationship with this binding.
+    ASSERT(external_sockets_.size() == 1);
+    talk_base::AsyncPacketSocket* socket = external_sockets_[0];
+    bool allocated;
+    talk_base::SocketAddressPair ap(
+        ext_addr, socket->GetLocalAddress(&allocated));
+    ASSERT(allocated);
+    ext_conn = new RelayServerConnection(int_conn->binding(), ap, socket);
+    ext_conn->binding()->AddExternalConnection(ext_conn);
+    AddConnection(ext_conn);
+  }
+
+  // If this connection has pinged us, then allow outgoing traffic.
+  if (ext_conn->locked())
+    ext_conn->Send(data_attr->bytes(), data_attr->length());
+
+  const StunUInt32Attribute* options_attr =
+      request.GetUInt32(STUN_ATTR_OPTIONS);
+  if (options_attr && (options_attr->value() & 0x01)) {
+    int_conn->set_default_destination(ext_addr);
+    int_conn->Lock();
+
+    StunMessage response;
+    response.SetType(STUN_SEND_RESPONSE);
+    response.SetTransactionID(request.transaction_id());
+
+    StunByteStringAttribute* magic_cookie_attr =
+        StunAttribute::CreateByteString(cricket::STUN_ATTR_MAGIC_COOKIE);
+    magic_cookie_attr->CopyBytes(int_conn->binding()->magic_cookie().c_str(),
+                                 int_conn->binding()->magic_cookie().size());
+    response.AddAttribute(magic_cookie_attr);
+
+    StunUInt32Attribute* options2_attr =
+      StunAttribute::CreateUInt32(cricket::STUN_ATTR_OPTIONS);
+    options2_attr->SetValue(0x01);
+    response.AddAttribute(options2_attr);
+
+    int_conn->SendStun(response);
+  }
+}
+
+void RelayServer::AddConnection(RelayServerConnection* conn) {
+  ASSERT(connections_.find(conn->addr_pair()) == connections_.end());
+  connections_[conn->addr_pair()] = conn;
+}
+
+void RelayServer::RemoveConnection(RelayServerConnection* conn) {
+  ConnectionMap::iterator iter = connections_.find(conn->addr_pair());
+  ASSERT(iter != connections_.end());
+  connections_.erase(iter);
+}
+
+void RelayServer::RemoveBinding(RelayServerBinding* binding) {
+  BindingMap::iterator iter = bindings_.find(binding->username());
+  ASSERT(iter != bindings_.end());
+  bindings_.erase(iter);
+
+  if (log_bindings_) {
+    LOG(LS_INFO) << "Removed binding " << binding->username() << ", "
+                 << bindings_.size() << " remaining";
+  }
+}
+
+void RelayServer::OnMessage(talk_base::Message *pmsg) {
+  ASSERT(pmsg->message_id == kMessageAcceptConnection);
+  talk_base::MessageData* data = pmsg->pdata;
+  talk_base::AsyncSocket* socket =
+      static_cast <talk_base::TypedMessageData<talk_base::AsyncSocket*>*>
+      (data)->data();
+  AcceptConnection(socket);
+  delete data;
+}
+
+void RelayServer::OnTimeout(RelayServerBinding* binding) {
+  // This call will result in all of the necessary clean-up. We can't call
+  // delete here, because you can't delete an object that is signaling you.
+  thread_->Dispose(binding);
+}
+
+void RelayServer::AcceptConnection(talk_base::AsyncSocket* server_socket) {
+  // Check if someone is trying to connect to us.
+  talk_base::SocketAddress accept_addr;
+  talk_base::AsyncSocket* accepted_socket =
+      server_socket->Accept(&accept_addr);
+  if (accepted_socket != NULL) {
+    // We had someone trying to connect, now check which protocol to
+    // use and create a packet socket.
+    ASSERT(server_sockets_[server_socket] == cricket::PROTO_TCP ||
+           server_sockets_[server_socket] == cricket::PROTO_SSLTCP);
+    if (server_sockets_[server_socket] == cricket::PROTO_SSLTCP) {
+      accepted_socket = new talk_base::AsyncSSLServerSocket(accepted_socket);
+    }
+    talk_base::AsyncTCPSocket* tcp_socket =
+        new talk_base::AsyncTCPSocket(accepted_socket, false);
+
+    // Finally add the socket so it can start communicating with the client.
+    AddInternalSocket(tcp_socket);
+  }
+}
+
+RelayServerConnection::RelayServerConnection(
+    RelayServerBinding* binding, const talk_base::SocketAddressPair& addrs,
+    talk_base::AsyncPacketSocket* socket)
+  : binding_(binding), addr_pair_(addrs), socket_(socket), locked_(false) {
+  // The creation of a new connection constitutes a use of the binding.
+  binding_->NoteUsed();
+}
+
+RelayServerConnection::~RelayServerConnection() {
+  // Remove this connection from the server's map (if it exists there).
+  binding_->server()->RemoveConnection(this);
+}
+
+void RelayServerConnection::Send(const char* data, size_t size) {
+  // Note that the binding has been used again.
+  binding_->NoteUsed();
+
+  cricket::Send(socket_, data, size, addr_pair_.source());
+}
+
+void RelayServerConnection::Send(
+    const char* data, size_t size, const talk_base::SocketAddress& from_addr) {
+  // If the from address is known to the client, we don't need to send it.
+  if (locked() && (from_addr == default_dest_)) {
+    Send(data, size);
+    return;
+  }
+
+  // Wrap the given data in a data-indication packet.
+
+  StunMessage msg;
+  msg.SetType(STUN_DATA_INDICATION);
+  msg.SetTransactionID("0000000000000000");
+
+  StunByteStringAttribute* magic_cookie_attr =
+      StunAttribute::CreateByteString(cricket::STUN_ATTR_MAGIC_COOKIE);
+  magic_cookie_attr->CopyBytes(binding_->magic_cookie().c_str(),
+                               binding_->magic_cookie().size());
+  msg.AddAttribute(magic_cookie_attr);
+
+  StunAddressAttribute* addr_attr =
+      StunAttribute::CreateAddress(STUN_ATTR_SOURCE_ADDRESS2);
+  addr_attr->SetFamily(1);
+  addr_attr->SetIP(from_addr.ip());
+  addr_attr->SetPort(from_addr.port());
+  msg.AddAttribute(addr_attr);
+
+  StunByteStringAttribute* data_attr =
+      StunAttribute::CreateByteString(STUN_ATTR_DATA);
+  ASSERT(size <= 65536);
+  data_attr->CopyBytes(data, uint16(size));
+  msg.AddAttribute(data_attr);
+
+  SendStun(msg);
+}
+
+void RelayServerConnection::SendStun(const StunMessage& msg) {
+  // Note that the binding has been used again.
+  binding_->NoteUsed();
+
+  cricket::SendStun(msg, socket_, addr_pair_.source());
+}
+
+void RelayServerConnection::SendStunError(
+      const StunMessage& request, int error_code, const char* error_desc) {
+  // An error does not indicate use.  If no legitimate use off the binding
+  // occurs, we want it to be cleaned up even if errors are still occuring.
+
+  cricket::SendStunError(
+      request, socket_, addr_pair_.source(), error_code, error_desc,
+      binding_->magic_cookie());
+}
+
+void RelayServerConnection::Lock() {
+  locked_ = true;
+}
+
+void RelayServerConnection::Unlock() {
+  locked_ = false;
+}
+
+// IDs used for posted messages:
+const uint32 MSG_LIFETIME_TIMER = 1;
+
+RelayServerBinding::RelayServerBinding(
+    RelayServer* server, const std::string& username,
+    const std::string& password, uint32 lifetime)
+  : server_(server), username_(username), password_(password),
+    lifetime_(lifetime) {
+  // For now, every connection uses the standard magic cookie value.
+  magic_cookie_.append(
+      reinterpret_cast<const char*>(STUN_MAGIC_COOKIE_VALUE), 4);
+
+  // Initialize the last-used time to now.
+  NoteUsed();
+
+  // Set the first timeout check.
+  server_->thread()->PostDelayed(lifetime_, this, MSG_LIFETIME_TIMER);
+}
+
+RelayServerBinding::~RelayServerBinding() {
+  // Clear the outstanding timeout check.
+  server_->thread()->Clear(this);
+
+  // Clean up all of the connections.
+  for (size_t i = 0; i < internal_connections_.size(); ++i)
+    delete internal_connections_[i];
+  for (size_t i = 0; i < external_connections_.size(); ++i)
+    delete external_connections_[i];
+
+  // Remove this binding from the server's map.
+  server_->RemoveBinding(this);
+}
+
+void RelayServerBinding::AddInternalConnection(RelayServerConnection* conn) {
+  internal_connections_.push_back(conn);
+}
+
+void RelayServerBinding::AddExternalConnection(RelayServerConnection* conn) {
+  external_connections_.push_back(conn);
+}
+
+void RelayServerBinding::NoteUsed() {
+  last_used_ = talk_base::Time();
+}
+
+bool RelayServerBinding::HasMagicCookie(const char* bytes, size_t size) const {
+  if (size < 24 + magic_cookie_.size()) {
+    return false;
+  } else {
+    return 0 == std::memcmp(
+        bytes + 24, magic_cookie_.c_str(), magic_cookie_.size());
+  }
+}
+
+RelayServerConnection* RelayServerBinding::GetInternalConnection(
+    const talk_base::SocketAddress& ext_addr) {
+
+  // Look for an internal connection that is locked to this address.
+  for (size_t i = 0; i < internal_connections_.size(); ++i) {
+    if (internal_connections_[i]->locked() &&
+        (ext_addr == internal_connections_[i]->default_destination()))
+      return internal_connections_[i];
+  }
+
+  // If one was not found, we send to the first connection.
+  ASSERT(internal_connections_.size() > 0);
+  return internal_connections_[0];
+}
+
+RelayServerConnection* RelayServerBinding::GetExternalConnection(
+    const talk_base::SocketAddress& ext_addr) {
+  for (size_t i = 0; i < external_connections_.size(); ++i) {
+    if (ext_addr == external_connections_[i]->addr_pair().source())
+      return external_connections_[i];
+  }
+  return 0;
+}
+
+void RelayServerBinding::OnMessage(talk_base::Message *pmsg) {
+  if (pmsg->message_id == MSG_LIFETIME_TIMER) {
+    ASSERT(!pmsg->pdata);
+
+    // If the lifetime timeout has been exceeded, then send a signal.
+    // Otherwise, just keep waiting.
+    if (talk_base::Time() >= last_used_ + lifetime_) {
+      LOG(LS_INFO) << "Expiring binding " << username_;
+      SignalTimeout(this);
+    } else {
+      server_->thread()->PostDelayed(lifetime_, this, MSG_LIFETIME_TIMER);
+    }
+
+  } else {
+    ASSERT(false);
+  }
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/relayserver.h b/talk/p2p/base/relayserver.h
new file mode 100644
index 0000000..d34c099
--- /dev/null
+++ b/talk/p2p/base/relayserver.h
@@ -0,0 +1,249 @@
+/*
+ * 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_P2P_BASE_RELAYSERVER_H_
+#define TALK_P2P_BASE_RELAYSERVER_H_
+
+#include <string>
+#include <vector>
+#include <map>
+
+#include "talk/base/asyncudpsocket.h"
+#include "talk/base/socketaddresspair.h"
+#include "talk/base/thread.h"
+#include "talk/base/time.h"
+#include "talk/p2p/base/port.h"
+#include "talk/p2p/base/stun.h"
+
+namespace cricket {
+
+class RelayServerBinding;
+class RelayServerConnection;
+
+// Relays traffic between connections to the server that are "bound" together.
+// All connections created with the same username/password are bound together.
+class RelayServer : public talk_base::MessageHandler,
+                    public sigslot::has_slots<> {
+ public:
+  // Creates a server, which will use this thread to post messages to itself.
+  explicit RelayServer(talk_base::Thread* thread);
+  ~RelayServer();
+
+  talk_base::Thread* thread() { return thread_; }
+
+  // Indicates whether we will print updates of the number of bindings.
+  bool log_bindings() const { return log_bindings_; }
+  void set_log_bindings(bool log_bindings) { log_bindings_ = log_bindings; }
+
+  // Updates the set of sockets that the server uses to talk to "internal"
+  // clients.  These are clients that do the "port allocations".
+  void AddInternalSocket(talk_base::AsyncPacketSocket* socket);
+  void RemoveInternalSocket(talk_base::AsyncPacketSocket* socket);
+
+  // Updates the set of sockets that the server uses to talk to "external"
+  // clients.  These are the clients that do not do allocations.  They do not
+  // know that these addresses represent a relay server.
+  void AddExternalSocket(talk_base::AsyncPacketSocket* socket);
+  void RemoveExternalSocket(talk_base::AsyncPacketSocket* socket);
+
+  // Starts listening for connections on this sockets. When someone
+  // tries to connect, the connection will be accepted and a new
+  // internal socket will be added.
+  void AddInternalServerSocket(talk_base::AsyncSocket* socket,
+                               cricket::ProtocolType proto);
+
+  // Removes this server socket from the list.
+  void RemoveInternalServerSocket(talk_base::AsyncSocket* socket);
+
+  // Methods for testing and debuging.
+  int GetConnectionCount() const;
+  talk_base::SocketAddressPair GetConnection(int connection) const;
+  bool HasConnection(const talk_base::SocketAddress& address) const;
+
+ private:
+  typedef std::vector<talk_base::AsyncPacketSocket*> SocketList;
+  typedef std::map<talk_base::AsyncSocket*,
+                   cricket::ProtocolType> ServerSocketMap;
+  typedef std::map<std::string, RelayServerBinding*> BindingMap;
+  typedef std::map<talk_base::SocketAddressPair,
+                   RelayServerConnection*> ConnectionMap;
+
+  talk_base::Thread* thread_;
+  bool log_bindings_;
+  SocketList internal_sockets_;
+  SocketList external_sockets_;
+  ServerSocketMap server_sockets_;
+  BindingMap bindings_;
+  ConnectionMap connections_;
+
+  // Called when a packet is received by the server on one of its sockets.
+  void OnInternalPacket(talk_base::AsyncPacketSocket* socket,
+                        const char* bytes, size_t size,
+                        const talk_base::SocketAddress& remote_addr);
+  void OnExternalPacket(talk_base::AsyncPacketSocket* socket,
+                        const char* bytes, size_t size,
+                        const talk_base::SocketAddress& remote_addr);
+
+  void OnReadEvent(talk_base::AsyncSocket* socket);
+
+  // Processes the relevant STUN request types from the client.
+  bool HandleStun(const char* bytes, size_t size,
+                  const talk_base::SocketAddress& remote_addr,
+                  talk_base::AsyncPacketSocket* socket,
+                  std::string* username, StunMessage* msg);
+  void HandleStunAllocate(const char* bytes, size_t size,
+                          const talk_base::SocketAddressPair& ap,
+                          talk_base::AsyncPacketSocket* socket);
+  void HandleStun(RelayServerConnection* int_conn, const char* bytes,
+                  size_t size);
+  void HandleStunAllocate(RelayServerConnection* int_conn,
+                          const StunMessage& msg);
+  void HandleStunSend(RelayServerConnection* int_conn, const StunMessage& msg);
+
+  // Adds/Removes the a connection or binding.
+  void AddConnection(RelayServerConnection* conn);
+  void RemoveConnection(RelayServerConnection* conn);
+  void RemoveBinding(RelayServerBinding* binding);
+
+  // Handle messages in our worker thread.
+  void OnMessage(talk_base::Message *pmsg);
+
+  // Called when the timer for checking lifetime times out.
+  void OnTimeout(RelayServerBinding* binding);
+
+  // Accept connections on this server socket.
+  void AcceptConnection(talk_base::AsyncSocket* server_socket);
+
+  friend class RelayServerConnection;
+  friend class RelayServerBinding;
+};
+
+// Maintains information about a connection to the server.  Each connection is
+// part of one and only one binding.
+class RelayServerConnection {
+ public:
+  RelayServerConnection(RelayServerBinding* binding,
+                        const talk_base::SocketAddressPair& addrs,
+                        talk_base::AsyncPacketSocket* socket);
+  ~RelayServerConnection();
+
+  RelayServerBinding* binding() { return binding_; }
+  talk_base::AsyncPacketSocket* socket() { return socket_; }
+
+  // Returns a pair where the source is the remote address and the destination
+  // is the local address.
+  const talk_base::SocketAddressPair& addr_pair() { return addr_pair_; }
+
+  // Sends a packet to the connected client.  If an address is provided, then
+  // we make sure the internal client receives it, wrapping if necessary.
+  void Send(const char* data, size_t size);
+  void Send(const char* data, size_t size,
+            const talk_base::SocketAddress& ext_addr);
+
+  // Sends a STUN message to the connected client with no wrapping.
+  void SendStun(const StunMessage& msg);
+  void SendStunError(const StunMessage& request, int code, const char* desc);
+
+  // A locked connection is one for which we know the intended destination of
+  // any raw packet received.
+  bool locked() const { return locked_; }
+  void Lock();
+  void Unlock();
+
+  // Records the address that raw packets should be forwarded to (for internal
+  // packets only; for external, we already know where they go).
+  const talk_base::SocketAddress& default_destination() const {
+    return default_dest_;
+  }
+  void set_default_destination(const talk_base::SocketAddress& addr) {
+    default_dest_ = addr;
+  }
+
+ private:
+  RelayServerBinding* binding_;
+  talk_base::SocketAddressPair addr_pair_;
+  talk_base::AsyncPacketSocket* socket_;
+  bool locked_;
+  talk_base::SocketAddress default_dest_;
+};
+
+// Records a set of internal and external connections that we relay between,
+// or in other words, that are "bound" together.
+class RelayServerBinding : public talk_base::MessageHandler {
+ public:
+  RelayServerBinding(
+      RelayServer* server, const std::string& username,
+      const std::string& password, uint32 lifetime);
+  virtual ~RelayServerBinding();
+
+  RelayServer* server() { return server_; }
+  uint32 lifetime() { return lifetime_; }
+  const std::string& username() { return username_; }
+  const std::string& password() { return password_; }
+  const std::string& magic_cookie() { return magic_cookie_; }
+
+  // Adds/Removes a connection into the binding.
+  void AddInternalConnection(RelayServerConnection* conn);
+  void AddExternalConnection(RelayServerConnection* conn);
+
+  // We keep track of the use of each binding.  If we detect that it was not
+  // used for longer than the lifetime, then we send a signal.
+  void NoteUsed();
+  sigslot::signal1<RelayServerBinding*> SignalTimeout;
+
+  // Determines whether the given packet has the magic cookie present (in the
+  // right place).
+  bool HasMagicCookie(const char* bytes, size_t size) const;
+
+  // Determines the connection to use to send packets to or from the given
+  // external address.
+  RelayServerConnection* GetInternalConnection(
+      const talk_base::SocketAddress& ext_addr);
+  RelayServerConnection* GetExternalConnection(
+      const talk_base::SocketAddress& ext_addr);
+
+  // MessageHandler:
+  void OnMessage(talk_base::Message *pmsg);
+
+ private:
+  RelayServer* server_;
+
+  std::string username_;
+  std::string password_;
+  std::string magic_cookie_;
+
+  std::vector<RelayServerConnection*> internal_connections_;
+  std::vector<RelayServerConnection*> external_connections_;
+
+  uint32 lifetime_;
+  uint32 last_used_;
+  // TODO: bandwidth
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_RELAYSERVER_H_
diff --git a/talk/p2p/base/relayserver_main.cc b/talk/p2p/base/relayserver_main.cc
new file mode 100644
index 0000000..2de4247
--- /dev/null
+++ b/talk/p2p/base/relayserver_main.cc
@@ -0,0 +1,80 @@
+/*
+ * 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 <iostream>  // NOLINT
+
+#include "talk/base/thread.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/p2p/base/relayserver.h"
+
+int main(int argc, char **argv) {
+  if (argc != 3) {
+    std::cerr << "usage: relayserver internal-address external-address"
+              << std::endl;
+    return 1;
+  }
+
+  talk_base::SocketAddress int_addr;
+  if (!int_addr.FromString(argv[1])) {
+    std::cerr << "Unable to parse IP address: " << argv[1];
+    return 1;
+  }
+
+  talk_base::SocketAddress ext_addr;
+  if (!ext_addr.FromString(argv[2])) {
+    std::cerr << "Unable to parse IP address: " << argv[2];
+    return 1;
+  }
+
+  talk_base::Thread *pthMain = talk_base::Thread::Current();
+
+  talk_base::scoped_ptr<talk_base::AsyncUDPSocket> int_socket(
+      talk_base::AsyncUDPSocket::Create(pthMain->socketserver(), int_addr));
+  if (!int_socket.get()) {
+    std::cerr << "Failed to create a UDP socket bound at"
+              << int_addr.ToString() << std::endl;
+    return 1;
+  }
+
+  talk_base::scoped_ptr<talk_base::AsyncUDPSocket> ext_socket(
+      talk_base::AsyncUDPSocket::Create(pthMain->socketserver(), ext_addr));
+  if (ext_socket.get()) {
+    std::cerr << "Failed to create a UDP socket bound at"
+              << ext_addr.ToString() << std::endl;
+    return 1;
+  }
+
+  cricket::RelayServer server(pthMain);
+  server.AddInternalSocket(int_socket.get());
+  server.AddExternalSocket(ext_socket.get());
+
+  std::cout << "Listening internally at " << int_addr.ToString() << std::endl;
+  std::cout << "Listening externally at " << ext_addr.ToString() << std::endl;
+
+  pthMain->Run();
+  return 0;
+}
diff --git a/talk/p2p/base/session.cc b/talk/p2p/base/session.cc
new file mode 100644
index 0000000..27b39dd
--- /dev/null
+++ b/talk/p2p/base/session.cc
@@ -0,0 +1,1091 @@
+/*
+ * 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/p2p/base/session.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/helpers.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/jid.h"
+#include "talk/p2p/base/sessionclient.h"
+#include "talk/p2p/base/transport.h"
+#include "talk/p2p/base/transportchannelproxy.h"
+#include "talk/p2p/base/p2ptransport.h"
+#include "talk/p2p/base/p2ptransportchannel.h"
+
+#include "talk/p2p/base/constants.h"
+
+namespace {
+
+const uint32 MSG_TIMEOUT = 1;
+const uint32 MSG_ERROR = 2;
+const uint32 MSG_STATE = 3;
+
+}  // namespace
+
+namespace cricket {
+
+bool BadMessage(const buzz::QName type,
+                const std::string& text,
+                MessageError* err) {
+  err->SetType(type);
+  err->SetText(text);
+  return false;
+}
+
+TransportProxy::~TransportProxy() {
+  for (ChannelMap::iterator iter = channels_.begin();
+       iter != channels_.end(); ++iter) {
+    iter->second->SignalDestroyed(iter->second);
+    delete iter->second;
+  }
+  delete transport_;
+}
+
+std::string TransportProxy::type() const {
+  return transport_->type();
+}
+
+TransportChannel* TransportProxy::GetChannel(const std::string& name) {
+  return GetProxy(name);
+}
+
+TransportChannel* TransportProxy::CreateChannel(
+    const std::string& name, const std::string& content_type) {
+  ASSERT(GetChannel(name) == NULL);
+  ASSERT(!transport_->HasChannel(name));
+
+  // We always create a proxy in case we need to change out the transport later.
+  TransportChannelProxy* channel =
+      new TransportChannelProxy(name, content_type);
+  channels_[name] = channel;
+
+  if (state_ == STATE_NEGOTIATED) {
+    SetProxyImpl(name, channel);
+  } else if (state_ == STATE_CONNECTING) {
+    GetOrCreateImpl(name, content_type);
+  }
+  return channel;
+}
+
+void TransportProxy::DestroyChannel(const std::string& name) {
+  TransportChannel* channel = GetChannel(name);
+  if (channel) {
+    channels_.erase(name);
+    channel->SignalDestroyed(channel);
+    delete channel;
+  }
+}
+
+void TransportProxy::SpeculativelyConnectChannels() {
+  ASSERT(state_ == STATE_INIT || state_ == STATE_CONNECTING);
+  state_ = STATE_CONNECTING;
+  for (ChannelMap::iterator iter = channels_.begin();
+       iter != channels_.end(); ++iter) {
+    GetOrCreateImpl(iter->first, iter->second->content_type());
+  }
+  transport_->ConnectChannels();
+}
+
+void TransportProxy::CompleteNegotiation() {
+  if (state_ != STATE_NEGOTIATED) {
+    state_ = STATE_NEGOTIATED;
+    for (ChannelMap::iterator iter = channels_.begin();
+         iter != channels_.end(); ++iter) {
+      SetProxyImpl(iter->first, iter->second);
+    }
+    transport_->ConnectChannels();
+  }
+}
+
+void TransportProxy::AddSentCandidates(const Candidates& candidates) {
+  for (Candidates::const_iterator cand = candidates.begin();
+       cand != candidates.end(); ++cand) {
+    sent_candidates_.push_back(*cand);
+  }
+}
+
+
+TransportChannelProxy* TransportProxy::GetProxy(const std::string& name) {
+  ChannelMap::iterator iter = channels_.find(name);
+  return (iter != channels_.end()) ? iter->second : NULL;
+}
+
+TransportChannelImpl* TransportProxy::GetOrCreateImpl(
+    const std::string& name, const std::string& content_type) {
+  TransportChannelImpl* impl = transport_->GetChannel(name);
+  if (impl == NULL) {
+    impl = transport_->CreateChannel(name, content_type);
+  }
+  return impl;
+}
+
+void TransportProxy::SetProxyImpl(
+    const std::string& name, TransportChannelProxy* proxy) {
+  TransportChannelImpl* impl = GetOrCreateImpl(name, proxy->content_type());
+  ASSERT(impl != NULL);
+  proxy->SetImplementation(impl);
+}
+
+
+
+
+BaseSession::BaseSession(talk_base::Thread *signaling_thread)
+    : state_(STATE_INIT), error_(ERROR_NONE),
+      local_description_(NULL), remote_description_(NULL),
+      signaling_thread_(signaling_thread) {
+}
+
+BaseSession::~BaseSession() {
+  delete remote_description_;
+  delete local_description_;
+}
+
+void BaseSession::SetState(State state) {
+  ASSERT(signaling_thread_->IsCurrent());
+  if (state != state_) {
+    state_ = state;
+    SignalState(this, state_);
+    signaling_thread_->Post(this, MSG_STATE);
+  }
+}
+
+void BaseSession::SetError(Error error) {
+  ASSERT(signaling_thread_->IsCurrent());
+  if (error != error_) {
+    error_ = error;
+    SignalError(this, error);
+  }
+}
+
+void BaseSession::OnMessage(talk_base::Message *pmsg) {
+  switch (pmsg->message_id) {
+  case MSG_TIMEOUT:
+    // Session timeout has occured.
+    SetError(ERROR_TIME);
+    break;
+
+  case MSG_ERROR:
+    TerminateWithReason(STR_TERMINATE_ERROR);
+    break;
+
+  case MSG_STATE:
+    switch (state_) {
+    case STATE_SENTACCEPT:
+    case STATE_RECEIVEDACCEPT:
+      SetState(STATE_INPROGRESS);
+      break;
+
+    case STATE_SENTREJECT:
+    case STATE_RECEIVEDREJECT:
+      // Assume clean termination.
+      Terminate();
+      break;
+
+    default:
+      // Explicitly ignoring some states here.
+      break;
+    }
+    break;
+  }
+}
+
+
+Session::Session(SessionManager *session_manager,
+                 const std::string& local_name,
+                 const std::string& initiator_name,
+                 const std::string& sid, const std::string& content_type,
+                 SessionClient* client) :
+    BaseSession(session_manager->signaling_thread()) {
+  ASSERT(session_manager->signaling_thread()->IsCurrent());
+  ASSERT(client != NULL);
+  session_manager_ = session_manager;
+  local_name_ = local_name;
+  sid_ = sid;
+  initiator_name_ = initiator_name;
+  content_type_ = content_type;
+  // TODO: Once we support different transport types,
+  // don't hard code this here.
+  transport_type_ = NS_GINGLE_P2P;
+  transport_parser_ = new P2PTransportParser();
+  client_ = client;
+  error_ = ERROR_NONE;
+  state_ = STATE_INIT;
+  initiator_ = false;
+  current_protocol_ = PROTOCOL_HYBRID;
+}
+
+Session::~Session() {
+  ASSERT(signaling_thread_->IsCurrent());
+
+  ASSERT(state_ != STATE_DEINIT);
+  state_ = STATE_DEINIT;
+  SignalState(this, state_);
+
+  for (TransportMap::iterator iter = transports_.begin();
+       iter != transports_.end(); ++iter) {
+    delete iter->second;
+  }
+
+  delete transport_parser_;
+}
+
+Transport* Session::GetTransport(const std::string& content_name) {
+  TransportProxy* transproxy = GetTransportProxy(content_name);
+  if (transproxy == NULL)
+    return NULL;
+  return transproxy->impl();
+}
+
+void Session::set_allow_local_ips(bool allow) {
+  allow_local_ips_ = allow;
+  for (TransportMap::iterator iter = transports_.begin();
+       iter != transports_.end(); ++iter) {
+    iter->second->impl()->set_allow_local_ips(allow);
+  }
+}
+
+bool Session::Initiate(const std::string &to,
+                       const SessionDescription* sdesc) {
+  ASSERT(signaling_thread_->IsCurrent());
+  SessionError error;
+
+  // Only from STATE_INIT
+  if (state_ != STATE_INIT)
+    return false;
+
+  // Setup for signaling.
+  remote_name_ = to;
+  initiator_ = true;
+  set_local_description(sdesc);
+  if (!CreateTransportProxies(GetEmptyTransportInfos(sdesc->contents()),
+                              &error)) {
+    LOG(LS_ERROR) << "Could not create transports: " << error.text;
+    return false;
+  }
+
+  if (!SendInitiateMessage(sdesc, &error)) {
+    LOG(LS_ERROR) << "Could not send initiate message: " << error.text;
+    return false;
+  }
+
+  SetState(Session::STATE_SENTINITIATE);
+
+  SpeculativelyConnectAllTransportChannels();
+  return true;
+}
+
+bool Session::Accept(const SessionDescription* sdesc) {
+  ASSERT(signaling_thread_->IsCurrent());
+
+  // Only if just received initiate
+  if (state_ != STATE_RECEIVEDINITIATE)
+    return false;
+
+  // Setup for signaling.
+  initiator_ = false;
+  set_local_description(sdesc);
+
+  SessionError error;
+  if (!SendAcceptMessage(sdesc, &error)) {
+    LOG(LS_ERROR) << "Could not send accept message: " << error.text;
+    return false;
+  }
+
+  SetState(Session::STATE_SENTACCEPT);
+  return true;
+}
+
+bool Session::Reject(const std::string& reason) {
+  ASSERT(signaling_thread_->IsCurrent());
+
+  // Reject is sent in response to an initiate or modify, to reject the
+  // request
+  if (state_ != STATE_RECEIVEDINITIATE && state_ != STATE_RECEIVEDMODIFY)
+    return false;
+
+  // Setup for signaling.
+  initiator_ = false;
+
+  SessionError error;
+  if (!SendRejectMessage(reason, &error)) {
+    LOG(LS_ERROR) << "Could not send reject message: " << error.text;
+    return false;
+  }
+
+  SetState(STATE_SENTREJECT);
+  return true;
+}
+
+bool Session::TerminateWithReason(const std::string& reason) {
+  ASSERT(signaling_thread_->IsCurrent());
+
+  // Either side can terminate, at any time.
+  switch (state_) {
+    case STATE_SENTTERMINATE:
+    case STATE_RECEIVEDTERMINATE:
+      return false;
+
+    case STATE_SENTREJECT:
+    case STATE_RECEIVEDREJECT:
+      // We don't need to send terminate if we sent or received a reject...
+      // it's implicit.
+      break;
+
+    default:
+      SessionError error;
+      if (!SendTerminateMessage(reason, &error)) {
+        LOG(LS_ERROR) << "Could not send terminate message: " << error.text;
+        return false;
+      }
+      break;
+  }
+
+  SetState(STATE_SENTTERMINATE);
+  return true;
+}
+
+bool Session::SendInfoMessage(const XmlElements& elems) {
+  ASSERT(signaling_thread_->IsCurrent());
+  SessionError error;
+  if (!SendMessage(ACTION_SESSION_INFO, elems, &error)) {
+    LOG(LS_ERROR) << "Could not send info message " << error.text;
+    return false;
+  }
+  return true;
+}
+
+
+TransportProxy* Session::GetTransportProxy(const Transport* transport) {
+  for (TransportMap::iterator iter = transports_.begin();
+       iter != transports_.end(); ++iter) {
+    TransportProxy* transproxy = iter->second;
+    if (transproxy->impl() == transport) {
+      return transproxy;
+    }
+  }
+  return NULL;
+}
+
+TransportProxy* Session::GetTransportProxy(const std::string& content_name) {
+  TransportMap::iterator iter = transports_.find(content_name);
+  return (iter != transports_.end()) ? iter->second : NULL;
+}
+
+TransportProxy* Session::GetFirstTransportProxy() {
+  if (transports_.empty())
+    return NULL;
+  return transports_.begin()->second;
+}
+
+TransportInfos Session::GetEmptyTransportInfos(
+    const ContentInfos& contents) const {
+  TransportInfos tinfos;
+  for (ContentInfos::const_iterator content = contents.begin();
+       content != contents.end(); ++content) {
+    tinfos.push_back(
+        TransportInfo(content->name, transport_type_, Candidates()));
+  }
+  return tinfos;
+}
+
+
+bool Session::OnRemoteCandidates(
+    const TransportInfos& tinfos, ParseError* error) {
+  for (TransportInfos::const_iterator tinfo = tinfos.begin();
+       tinfo != tinfos.end(); ++tinfo) {
+    TransportProxy* transproxy = GetTransportProxy(tinfo->content_name);
+    if (transproxy == NULL) {
+      return BadParse("Unknown content name: " + tinfo->content_name, error);
+    }
+
+    // Must complete negotiation before sending remote candidates, or
+    // there won't be any channel impls.
+    transproxy->CompleteNegotiation();
+    for (Candidates::const_iterator cand = tinfo->candidates.begin();
+         cand != tinfo->candidates.end(); ++cand) {
+      if (!transproxy->impl()->VerifyCandidate(*cand, error))
+        return false;
+
+      if (!transproxy->impl()->HasChannel(cand->name())) {
+        buzz::XmlElement* extra_info =
+            new buzz::XmlElement(QN_GINGLE_P2P_UNKNOWN_CHANNEL_NAME);
+        extra_info->AddAttr(buzz::QN_NAME, cand->name());
+        error->extra = extra_info;
+
+        return BadParse("channel named in candidate does not exist: " +
+                        cand->name() + " for content: "+ tinfo->content_name,
+                        error);
+      }
+    }
+    transproxy->impl()->OnRemoteCandidates(tinfo->candidates);
+  }
+
+  return true;
+}
+
+
+TransportProxy* Session::GetOrCreateTransportProxy(
+    const std::string& content_name) {
+  TransportProxy* transproxy = GetTransportProxy(content_name);
+  if (transproxy)
+    return transproxy;
+
+  Transport* transport =
+      new P2PTransport(signaling_thread_,
+                       session_manager_->worker_thread(),
+                       session_manager_->port_allocator());
+  transport->set_allow_local_ips(allow_local_ips_);
+  transport->SignalConnecting.connect(
+      this, &Session::OnTransportConnecting);
+  transport->SignalWritableState.connect(
+      this, &Session::OnTransportWritable);
+  transport->SignalRequestSignaling.connect(
+      this, &Session::OnTransportRequestSignaling);
+  transport->SignalCandidatesReady.connect(
+      this, &Session::OnTransportCandidatesReady);
+  transport->SignalTransportError.connect(
+      this, &Session::OnTransportSendError);
+  transport->SignalChannelGone.connect(
+      this, &Session::OnTransportChannelGone);
+
+  transproxy = new TransportProxy(content_name, transport);
+  transports_[content_name] = transproxy;
+
+  return transproxy;
+}
+
+bool Session::CreateTransportProxies(const TransportInfos& tinfos,
+                                     SessionError* error) {
+  for (TransportInfos::const_iterator tinfo = tinfos.begin();
+       tinfo != tinfos.end(); ++tinfo) {
+    if (tinfo->transport_type != transport_type_) {
+      error->SetText("No supported transport in offer.");
+      return false;
+    }
+
+    GetOrCreateTransportProxy(tinfo->content_name);
+  }
+  return true;
+}
+
+void Session::SpeculativelyConnectAllTransportChannels() {
+  for (TransportMap::iterator iter = transports_.begin();
+       iter != transports_.end(); ++iter) {
+    iter->second->SpeculativelyConnectChannels();
+  }
+}
+
+TransportParserMap Session::GetTransportParsers() {
+  TransportParserMap parsers;
+  parsers[transport_type_] = transport_parser_;
+  return parsers;
+}
+
+ContentParserMap Session::GetContentParsers() {
+  ContentParserMap parsers;
+  parsers[content_type_] = client_;
+  return parsers;
+}
+
+TransportChannel* Session::CreateChannel(const std::string& content_name,
+                                         const std::string& channel_name) {
+  // We create the proxy "on demand" here because we need to support
+  // creating channels at any time, even before we send or receive
+  // initiate messages, which is before we create the transports.
+  TransportProxy* transproxy = GetOrCreateTransportProxy(content_name);
+  return transproxy->CreateChannel(channel_name, content_type_);
+}
+
+TransportChannel* Session::GetChannel(const std::string& content_name,
+                                      const std::string& channel_name) {
+  TransportProxy* transproxy = GetTransportProxy(content_name);
+  if (transproxy == NULL)
+    return NULL;
+  else
+    return transproxy->GetChannel(channel_name);
+}
+
+void Session::DestroyChannel(const std::string& content_name,
+                             const std::string& channel_name) {
+  TransportProxy* transproxy = GetTransportProxy(content_name);
+  ASSERT(transproxy != NULL);
+  transproxy->DestroyChannel(channel_name);
+}
+
+void Session::OnSignalingReady() {
+  ASSERT(signaling_thread_->IsCurrent());
+  for (TransportMap::iterator iter = transports_.begin();
+       iter != transports_.end(); ++iter) {
+    iter->second->impl()->OnSignalingReady();
+  }
+}
+
+void Session::OnTransportConnecting(Transport* transport) {
+  // This is an indication that we should begin watching the writability
+  // state of the transport.
+  OnTransportWritable(transport);
+}
+
+void Session::OnTransportWritable(Transport* transport) {
+  ASSERT(signaling_thread_->IsCurrent());
+
+  // If the transport is not writable, start a timer to make sure that it
+  // becomes writable within a reasonable amount of time.  If it does not, we
+  // terminate since we can't actually send data.  If the transport is writable,
+  // cancel the timer.  Note that writability transitions may occur repeatedly
+  // during the lifetime of the session.
+  signaling_thread_->Clear(this, MSG_TIMEOUT);
+  if (transport->HasChannels() && !transport->writable()) {
+    signaling_thread_->PostDelayed(
+        session_manager_->session_timeout() * 1000, this, MSG_TIMEOUT);
+  }
+}
+
+void Session::OnTransportRequestSignaling(Transport* transport) {
+  ASSERT(signaling_thread_->IsCurrent());
+  SignalRequestSignaling(this);
+}
+
+void Session::OnTransportCandidatesReady(Transport* transport,
+                                         const Candidates& candidates) {
+  ASSERT(signaling_thread_->IsCurrent());
+  TransportProxy* transproxy = GetTransportProxy(transport);
+  if (transproxy != NULL) {
+    if (!transproxy->negotiated()) {
+      transproxy->AddSentCandidates(candidates);
+    }
+    SessionError error;
+    if (!SendTransportInfoMessage(
+            TransportInfo(transproxy->content_name(), transproxy->type(),
+                          candidates),
+            &error)) {
+      LOG(LS_ERROR) << "Could not send transport info message: "
+                    << error.text;
+      return;
+    }
+  }
+}
+
+void Session::OnTransportSendError(Transport* transport,
+                                   const buzz::XmlElement* stanza,
+                                   const buzz::QName& name,
+                                   const std::string& type,
+                                   const std::string& text,
+                                   const buzz::XmlElement* extra_info) {
+  ASSERT(signaling_thread_->IsCurrent());
+  SignalErrorMessage(this, stanza, name, type, text, extra_info);
+}
+
+void Session::OnTransportChannelGone(Transport* transport,
+                                     const std::string& name) {
+  ASSERT(signaling_thread_->IsCurrent());
+  SignalChannelGone(this, name);
+}
+
+void Session::OnIncomingMessage(const SessionMessage& msg) {
+  ASSERT(signaling_thread_->IsCurrent());
+  ASSERT(state_ == STATE_INIT || msg.from == remote_name_);
+
+  if (current_protocol_== PROTOCOL_HYBRID) {
+    if (msg.protocol == PROTOCOL_GINGLE) {
+      current_protocol_ = PROTOCOL_GINGLE;
+    } else {
+      current_protocol_ = PROTOCOL_JINGLE;
+    }
+  }
+
+  bool valid = false;
+  MessageError error;
+  switch (msg.type) {
+    case ACTION_SESSION_INITIATE:
+      valid = OnInitiateMessage(msg, &error);
+      break;
+    case ACTION_SESSION_INFO:
+      valid = OnInfoMessage(msg);
+      break;
+    case ACTION_SESSION_ACCEPT:
+      valid = OnAcceptMessage(msg, &error);
+      break;
+    case ACTION_SESSION_REJECT:
+      valid = OnRejectMessage(msg, &error);
+      break;
+    case ACTION_SESSION_TERMINATE:
+      valid = OnTerminateMessage(msg, &error);
+      break;
+    case ACTION_TRANSPORT_INFO:
+      valid = OnTransportInfoMessage(msg, &error);
+      break;
+    case ACTION_TRANSPORT_ACCEPT:
+      valid = OnTransportAcceptMessage(msg, &error);
+      break;
+    case ACTION_NOTIFY:
+      valid = OnNotifyMessage(msg, &error);
+      break;
+    case ACTION_UPDATE:
+      valid = OnUpdateMessage(msg, &error);
+      break;
+    default:
+      valid = BadMessage(buzz::QN_STANZA_BAD_REQUEST,
+                         "unknown session message type",
+                         &error);
+  }
+
+  if (valid) {
+    SendAcknowledgementMessage(msg.stanza);
+  } else {
+    SignalErrorMessage(this, msg.stanza, error.type,
+                       "modify", error.text, NULL);
+  }
+}
+
+void Session::OnFailedSend(const buzz::XmlElement* orig_stanza,
+                           const buzz::XmlElement* error_stanza) {
+  ASSERT(signaling_thread_->IsCurrent());
+
+  SessionMessage msg;
+  ParseError parse_error;
+  if (!ParseSessionMessage(orig_stanza, &msg, &parse_error)) {
+    LOG(LS_ERROR) << "Error parsing failed send: " << parse_error.text
+                  << ":" << orig_stanza;
+    return;
+  }
+
+  // If the error is a session redirect, call OnRedirectError, which will
+  // continue the session with a new remote JID.
+  SessionRedirect redirect;
+  if (FindSessionRedirect(error_stanza, &redirect)) {
+    SessionError error;
+    if (!OnRedirectError(redirect, &error)) {
+      // TODO: Should we send a message back?  The standard
+      // says nothing about it.
+      LOG(LS_ERROR) << "Failed to redirect: " << error.text;
+      SetError(ERROR_RESPONSE);
+    }
+    return;
+  }
+
+  std::string error_type = "cancel";
+
+  const buzz::XmlElement* error = error_stanza->FirstNamed(buzz::QN_ERROR);
+  if (error) {
+    ASSERT(error->HasAttr(buzz::QN_TYPE));
+    error_type = error->Attr(buzz::QN_TYPE);
+
+    LOG(LS_ERROR) << "Session error:\n" << error->Str() << "\n"
+                  << "in response to:\n" << orig_stanza->Str();
+  } else {
+    // don't crash if <error> is missing
+    LOG(LS_ERROR) << "Session error without <error/> element, ignoring";
+    return;
+  }
+
+  if (msg.type == ACTION_TRANSPORT_INFO) {
+    // Transport messages frequently generate errors because they are sent right
+    // when we detect a network failure.  For that reason, we ignore such
+    // errors, because if we do not establish writability again, we will
+    // terminate anyway.  The exceptions are transport-specific error tags,
+    // which we pass on to the respective transport.
+
+    // TODO: This is only used for unknown channel name.
+    // For Jingle, find a stanard-compliant way of doing this.  For
+    // Gingle, guess the content name based on the channel name.
+    for (const buzz::XmlElement* elem = error->FirstElement();
+         NULL != elem; elem = elem->NextElement()) {
+      TransportProxy* transproxy = GetFirstTransportProxy();
+      if (transproxy && transproxy->type() == error->Name().Namespace()) {
+        transproxy->impl()->OnTransportError(elem);
+      }
+    }
+  } else if ((error_type != "continue") && (error_type != "wait")) {
+    // We do not set an error if the other side said it is okay to continue
+    // (possibly after waiting).  These errors can be ignored.
+    SetError(ERROR_RESPONSE);
+  }
+}
+
+bool Session::OnInitiateMessage(const SessionMessage& msg,
+                                MessageError* error) {
+  if (!CheckState(STATE_INIT, error))
+    return false;
+
+  SessionInitiate init;
+  if (!ParseSessionInitiate(msg.protocol, msg.action_elem,
+                            GetContentParsers(), GetTransportParsers(),
+                            &init, error))
+    return false;
+
+  SessionError session_error;
+  if (!CreateTransportProxies(init.transports, &session_error)) {
+    return BadMessage(buzz::QN_STANZA_NOT_ACCEPTABLE,
+                      session_error.text, error);
+  }
+
+  initiator_ = false;
+  remote_name_ = msg.from;
+  set_remote_description(new SessionDescription(init.ClearContents()));
+  SetState(STATE_RECEIVEDINITIATE);
+
+  // Users of Session may listen to state change and call Reject().
+  if (state_ != STATE_SENTREJECT) {
+    if (!OnRemoteCandidates(init.transports, error))
+      return false;
+  }
+  return true;
+}
+
+bool Session::OnAcceptMessage(const SessionMessage& msg, MessageError* error) {
+  if (!CheckState(STATE_SENTINITIATE, error))
+    return false;
+
+  SessionAccept accept;
+  if (!ParseSessionAccept(msg.protocol, msg.action_elem,
+                          GetContentParsers(), GetTransportParsers(),
+                          &accept, error))
+    return false;
+
+  set_remote_description(new SessionDescription(accept.ClearContents()));
+  SetState(STATE_RECEIVEDACCEPT);
+
+  // Users of Session may listen to state change and call Reject().
+  if (state_ != STATE_SENTREJECT) {
+    if (!OnRemoteCandidates(accept.transports, error))
+      return false;
+  }
+
+  return true;
+}
+
+bool Session::OnRejectMessage(const SessionMessage& msg, MessageError* error) {
+  if (!CheckState(STATE_SENTINITIATE, error))
+    return false;
+
+  SetState(STATE_RECEIVEDREJECT);
+  return true;
+}
+
+// Only used by app/win32/fileshare.cc.
+bool Session::OnInfoMessage(const SessionMessage& msg) {
+  SignalInfoMessage(this, CopyOfXmlChildren(msg.action_elem));
+  return true;
+}
+
+bool Session::OnTerminateMessage(const SessionMessage& msg,
+                                 MessageError* error) {
+  SessionTerminate term;
+  if (!ParseSessionTerminate(msg.protocol, msg.action_elem, &term, error))
+    return false;
+
+  SignalReceivedTerminateReason(this, term.reason);
+  if (term.debug_reason != buzz::STR_EMPTY) {
+    LOG(LS_VERBOSE) << "Received error on call: " << term.debug_reason;
+  }
+
+  SetState(STATE_RECEIVEDTERMINATE);
+  return true;
+}
+
+bool Session::OnTransportInfoMessage(const SessionMessage& msg,
+                                     MessageError* error) {
+  TransportInfos tinfos;
+  if (!ParseTransportInfos(msg.protocol, msg.action_elem,
+                           initiator_description()->contents(),
+                           GetTransportParsers(), &tinfos, error))
+    return false;
+
+  if (!OnRemoteCandidates(tinfos, error))
+    return false;
+
+  return true;
+}
+
+bool Session::OnTransportAcceptMessage(const SessionMessage& msg,
+                                       MessageError* error) {
+  // TODO: Currently here only for compatibility with
+  // Gingle 1.1 clients (notably, Google Voice).
+  return true;
+}
+
+bool Session::OnNotifyMessage(const SessionMessage& msg,
+                              MessageError* error) {
+  SessionNotify notify;
+  if (!ParseSessionNotify(msg.action_elem, &notify, error)) {
+    return false;
+  }
+
+  SignalMediaSources(notify.nickname_to_sources);
+
+  return true;
+}
+
+bool Session::OnUpdateMessage(const SessionMessage& msg,
+                              MessageError* error) {
+  SessionUpdate update;
+  if (!ParseSessionUpdate(msg.action_elem, &update, error)) {
+    return false;
+  }
+
+  // TODO: Process this message appropriately.
+
+  return true;
+}
+
+bool BareJidsEqual(const std::string& name1,
+                   const std::string& name2) {
+  buzz::Jid jid1(name1);
+  buzz::Jid jid2(name2);
+
+  return jid1.IsValid() && jid2.IsValid() && jid1.BareEquals(jid2);
+}
+
+bool Session::OnRedirectError(const SessionRedirect& redirect,
+                              SessionError* error) {
+  MessageError message_error;
+  if (!CheckState(STATE_SENTINITIATE, &message_error)) {
+    return BadWrite(message_error.text, error);
+  }
+
+  if (!BareJidsEqual(remote_name_, redirect.target))
+    return BadWrite("Redirection not allowed: must be the same bare jid.",
+                    error);
+
+  // When we receive a redirect, we point the session at the new JID
+  // and resend the candidates.
+  remote_name_ = redirect.target;
+  return (SendInitiateMessage(local_description(), error) &&
+          ResendAllTransportInfoMessages(error));
+}
+
+bool Session::CheckState(State state, MessageError* error) {
+  ASSERT(state_ == state);
+  if (state_ != state) {
+    return BadMessage(buzz::QN_STANZA_NOT_ALLOWED,
+                      "message not allowed in current state",
+                      error);
+  }
+  return true;
+}
+
+void Session::SetError(Error error) {
+  BaseSession::SetError(error);
+  if (error_ != ERROR_NONE)
+    signaling_thread_->Post(this, MSG_ERROR);
+}
+
+void Session::OnMessage(talk_base::Message *pmsg) {
+  // preserve this because BaseSession::OnMessage may modify it
+  BaseSession::State orig_state = state_;
+
+  BaseSession::OnMessage(pmsg);
+
+  switch (pmsg->message_id) {
+  case MSG_STATE:
+    switch (orig_state) {
+    case STATE_SENTTERMINATE:
+    case STATE_RECEIVEDTERMINATE:
+      session_manager_->DestroySession(this);
+      break;
+
+    default:
+      // Explicitly ignoring some states here.
+      break;
+    }
+    break;
+  }
+}
+
+bool Session::SendInitiateMessage(const SessionDescription* sdesc,
+                                  SessionError* error) {
+  SessionInitiate init;
+  init.contents = sdesc->contents();
+  init.transports = GetEmptyTransportInfos(init.contents);
+  return SendMessage(ACTION_SESSION_INITIATE, init, error);
+}
+
+bool Session::WriteSessionAction(
+    SignalingProtocol protocol, const SessionInitiate& init,
+    XmlElements* elems, WriteError* error) {
+  ContentParserMap content_parsers = GetContentParsers();
+  TransportParserMap trans_parsers = GetTransportParsers();
+
+  return WriteSessionInitiate(protocol, init.contents, init.transports,
+                              content_parsers, trans_parsers,
+                              elems, error);
+}
+
+bool Session::SetVideoView(
+    const std::vector<VideoViewRequest>& view_requests) {
+  SessionView view;
+  SessionError error;
+
+  view.view_requests = view_requests;
+
+  return !SendViewMessage(view, &error);
+}
+
+bool Session::SendAcceptMessage(const SessionDescription* sdesc,
+                                SessionError* error) {
+  XmlElements elems;
+  if (!WriteSessionAccept(current_protocol_,
+                          sdesc->contents(),
+                          GetEmptyTransportInfos(sdesc->contents()),
+                          GetContentParsers(), GetTransportParsers(),
+                          &elems, error)) {
+    return false;
+  }
+  return SendMessage(ACTION_SESSION_ACCEPT, elems, error);
+}
+
+bool Session::SendRejectMessage(const std::string& reason,
+                                SessionError* error) {
+  XmlElements elems;
+  return SendMessage(ACTION_SESSION_REJECT, elems, error);
+}
+
+
+bool Session::SendTerminateMessage(const std::string& reason,
+                                   SessionError* error) {
+  SessionTerminate term(reason);
+  return SendMessage(ACTION_SESSION_TERMINATE, term, error);
+}
+
+bool Session::WriteSessionAction(SignalingProtocol protocol,
+                                 const SessionTerminate& term,
+                                 XmlElements* elems, WriteError* error) {
+  WriteSessionTerminate(protocol, term, elems);
+  return true;
+}
+
+bool Session::SendTransportInfoMessage(const TransportInfo& tinfo,
+                                       SessionError* error) {
+  return SendMessage(ACTION_TRANSPORT_INFO, tinfo, error);
+}
+
+bool Session::WriteSessionAction(SignalingProtocol protocol,
+                                 const TransportInfo& tinfo,
+                                 XmlElements* elems, WriteError* error) {
+  TransportInfos tinfos;
+  tinfos.push_back(tinfo);
+  TransportParserMap parsers = GetTransportParsers();
+
+  return WriteTransportInfos(protocol, tinfos, parsers,
+                             elems, error);
+}
+
+bool Session::SendViewMessage(const SessionView& view, SessionError* error) {
+  XmlElements elems;
+  WriteSessionView(view, &elems);
+  return SendMessage(ACTION_VIEW, elems, error);
+}
+
+bool Session::ResendAllTransportInfoMessages(SessionError* error) {
+  for (TransportMap::iterator iter = transports_.begin();
+       iter != transports_.end(); ++iter) {
+    TransportProxy* transproxy = iter->second;
+    if (transproxy->sent_candidates().size() > 0) {
+      if (!SendTransportInfoMessage(
+              TransportInfo(
+                  transproxy->content_name(),
+                  transproxy->type(),
+                  transproxy->sent_candidates()),
+              error)) {
+        return false;
+      }
+      transproxy->ClearSentCandidates();
+    }
+  }
+  return true;
+}
+
+bool Session::SendMessage(ActionType type, const XmlElements& action_elems,
+                          SessionError* error) {
+  talk_base::scoped_ptr<buzz::XmlElement> stanza(
+      new buzz::XmlElement(buzz::QN_IQ));
+
+  SessionMessage msg(current_protocol_, type, sid_, initiator_name_);
+  msg.to = remote_name_;
+  WriteSessionMessage(msg, action_elems, stanza.get());
+
+  SignalOutgoingMessage(this, stanza.get());
+  return true;
+}
+
+template <typename Action>
+bool Session::SendMessage(ActionType type, const Action& action,
+                          SessionError* error) {
+  talk_base::scoped_ptr<buzz::XmlElement> stanza(
+      new buzz::XmlElement(buzz::QN_IQ));
+  if (!WriteActionMessage(type, action, stanza.get(), error))
+    return false;
+
+  SignalOutgoingMessage(this, stanza.get());
+  return true;
+}
+
+template <typename Action>
+bool Session::WriteActionMessage(ActionType type, const Action& action,
+                                 buzz::XmlElement* stanza,
+                                 WriteError* error) {
+  if (current_protocol_ == PROTOCOL_HYBRID) {
+    if (!WriteActionMessage(PROTOCOL_JINGLE, type, action, stanza, error))
+      return false;
+    if (!WriteActionMessage(PROTOCOL_GINGLE, type, action, stanza, error))
+      return false;
+  } else {
+    if (!WriteActionMessage(current_protocol_, type, action, stanza, error))
+      return false;
+  }
+  return true;
+}
+
+template <typename Action>
+bool Session::WriteActionMessage(SignalingProtocol protocol,
+                                 ActionType type, const Action& action,
+                                 buzz::XmlElement* stanza, WriteError* error) {
+  XmlElements action_elems;
+  if (!WriteSessionAction(protocol, action, &action_elems, error))
+    return false;
+
+  SessionMessage msg(protocol, type, sid_, initiator_name_);
+  msg.to = remote_name_;
+
+  WriteSessionMessage(msg, action_elems, stanza);
+  return true;
+}
+
+void Session::SendAcknowledgementMessage(const buzz::XmlElement* stanza) {
+  talk_base::scoped_ptr<buzz::XmlElement> ack(
+      new buzz::XmlElement(buzz::QN_IQ));
+  ack->SetAttr(buzz::QN_TO, remote_name_);
+  ack->SetAttr(buzz::QN_ID, stanza->Attr(buzz::QN_ID));
+  ack->SetAttr(buzz::QN_TYPE, "result");
+
+  SignalOutgoingMessage(this, ack.get());
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/session.h b/talk/p2p/base/session.h
new file mode 100644
index 0000000..b00e809
--- /dev/null
+++ b/talk/p2p/base/session.h
@@ -0,0 +1,551 @@
+/*
+ * 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_P2P_BASE_SESSION_H_
+#define TALK_P2P_BASE_SESSION_H_
+
+#include <list>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "talk/p2p/base/sessionmessages.h"
+#include "talk/p2p/base/sessionmanager.h"
+#include "talk/base/socketaddress.h"
+#include "talk/p2p/base/sessionclient.h"
+#include "talk/p2p/base/parsing.h"
+#include "talk/p2p/base/port.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/constants.h"
+
+namespace cricket {
+
+class P2PTransportChannel;
+class Transport;
+class TransportChannel;
+class TransportChannelProxy;
+class TransportChannelImpl;
+
+// Used for errors that will send back a specific error message to the
+// remote peer.  We add "type" to the errors because it's needed for
+// SignalErrorMessage.
+struct MessageError : ParseError {
+  buzz::QName type;
+
+  // if unset, assume type is a parse error
+  MessageError() : ParseError(), type(buzz::QN_STANZA_BAD_REQUEST) {}
+
+  void SetType(const buzz::QName type) {
+    this->type = type;
+  }
+};
+
+// Used for errors that may be returned by public session methods that
+// can fail.
+// TODO: Use this error in Session::Initiate and
+// Session::Accept.
+struct SessionError : WriteError {
+};
+
+// Bundles a Transport and ChannelMap together. ChannelMap is used to
+// create transport channels before receiving or sending a session
+// initiate, and for speculatively connecting channels.  Previously, a
+// session had one ChannelMap and transport.  Now, with multiple
+// transports per session, we need multiple ChannelMaps as well.
+class TransportProxy {
+ public:
+  TransportProxy(const std::string& content_name, Transport* transport)
+      : content_name_(content_name),
+        transport_(transport),
+        state_(STATE_INIT),
+        sent_candidates_(false) {}
+  ~TransportProxy();
+
+  std::string content_name() const { return content_name_; }
+  Transport* impl() const { return transport_; }
+  std::string type() const;
+  bool negotiated() const { return state_ == STATE_NEGOTIATED; }
+  const Candidates& sent_candidates() const { return sent_candidates_; }
+
+  TransportChannel* GetChannel(const std::string& name);
+  TransportChannel* CreateChannel(const std::string& name,
+                                  const std::string& content_type);
+  void DestroyChannel(const std::string& name);
+  void AddSentCandidates(const Candidates& candidates);
+  void ClearSentCandidates() { sent_candidates_.clear(); }
+  void SpeculativelyConnectChannels();
+  void CompleteNegotiation();
+
+ private:
+  enum TransportState {
+    STATE_INIT,
+    STATE_CONNECTING,
+    STATE_NEGOTIATED
+  };
+
+  typedef std::map<std::string, TransportChannelProxy*> ChannelMap;
+
+  TransportChannelProxy* GetProxy(const std::string& name);
+  TransportChannelImpl* GetOrCreateImpl(const std::string& name,
+                                        const std::string& content_type);
+  void SetProxyImpl(const std::string& name, TransportChannelProxy* proxy);
+
+  std::string content_name_;
+  Transport* transport_;
+  TransportState state_;
+  ChannelMap channels_;
+  Candidates sent_candidates_;
+};
+
+typedef std::map<std::string, TransportProxy*> TransportMap;
+
+// TODO: Consider simplifying the dependency from Voice/VideoChannel
+// on Session. Right now the Channel class requires a BaseSession, but it only
+// uses CreateChannel/DestroyChannel. Perhaps something like a
+// TransportChannelFactory could be hoisted up out of BaseSession, or maybe
+// the transports could be passed in directly.
+
+// A BaseSession manages general session state. This includes negotiation
+// of both the application-level and network-level protocols:  the former
+// defines what will be sent and the latter defines how it will be sent.  Each
+// network-level protocol is represented by a Transport object.  Each Transport
+// participates in the network-level negotiation.  The individual streams of
+// packets are represented by TransportChannels.  The application-level protocol
+// is represented by SessionDecription objects.
+class BaseSession : public sigslot::has_slots<>,
+                    public talk_base::MessageHandler {
+ public:
+  enum State {
+    STATE_INIT = 0,
+    STATE_SENTINITIATE,       // sent initiate, waiting for Accept or Reject
+    STATE_RECEIVEDINITIATE,   // received an initiate. Call Accept or Reject
+    STATE_SENTACCEPT,         // sent accept. begin connecting transport
+    STATE_RECEIVEDACCEPT,     // received accept. begin connecting transport
+    STATE_SENTMODIFY,         // sent modify, waiting for Accept or Reject
+    STATE_RECEIVEDMODIFY,     // received modify, call Accept or Reject
+    STATE_SENTREJECT,         // sent reject after receiving initiate
+    STATE_RECEIVEDREJECT,     // received reject after sending initiate
+    STATE_SENTREDIRECT,       // sent direct after receiving initiate
+    STATE_SENTTERMINATE,      // sent terminate (any time / either side)
+    STATE_RECEIVEDTERMINATE,  // received terminate (any time / either side)
+    STATE_INPROGRESS,         // session accepted and in progress
+    STATE_DEINIT,             // session is being destroyed
+  };
+
+  enum Error {
+    ERROR_NONE = 0,      // no error
+    ERROR_TIME = 1,      // no response to signaling
+    ERROR_RESPONSE = 2,  // error during signaling
+    ERROR_NETWORK = 3,   // network error, could not allocate network resources
+    ERROR_CONTENT = 4,   // channel errors in SetLocalContent/SetRemoteContent
+  };
+
+  explicit BaseSession(talk_base::Thread *signaling_thread);
+  virtual ~BaseSession();
+
+  // Updates the state, signaling if necessary.
+  void SetState(State state);
+
+  // Updates the error state, signaling if necessary.
+  virtual void SetError(Error error);
+
+  // Handles messages posted to us.
+  virtual void OnMessage(talk_base::Message *pmsg);
+
+  // Returns the current state of the session.  See the enum above for details.
+  // Each time the state changes, we will fire this signal.
+  State state() const { return state_; }
+  sigslot::signal2<BaseSession *, State> SignalState;
+
+  // Returns the last error in the session.  See the enum above for details.
+  // Each time the an error occurs, we will fire this signal.
+  Error error() const { return error_; }
+  sigslot::signal2<BaseSession *, Error> SignalError;
+
+  // Creates a new channel with the given names.  This method may be called
+  // immediately after creating the session.  However, the actual
+  // implementation may not be fixed until transport negotiation completes.
+  // This will usually be called from the worker thread, but that
+  // shouldn't be an issue since the main thread will be blocked in
+  // Send when doing so.
+  virtual TransportChannel* CreateChannel(const std::string& content_name,
+                                          const std::string& channel_name) = 0;
+
+  // Returns the channel with the given names.
+  virtual TransportChannel* GetChannel(const std::string& content_name,
+                                       const std::string& channel_name) = 0;
+
+  // Destroys the channel with the given names.
+  // This will usually be called from the worker thread, but that
+  // shouldn't be an issue since the main thread will be blocked in
+  // Send when doing so.
+  virtual void DestroyChannel(const std::string& content_name,
+                              const std::string& channel_name) = 0;
+
+  // Invoked when we notice that there is no matching channel on our peer.
+  sigslot::signal2<Session*, const std::string&> SignalChannelGone;
+
+  // Returns the application-level description given by our client.
+  // If we are the recipient, this will be NULL until we send an accept.
+  const SessionDescription* local_description() const {
+    return local_description_;
+  }
+  // Takes ownership of SessionDescription*
+  bool set_local_description(const SessionDescription* sdesc) {
+    if (sdesc != local_description_) {
+      delete local_description_;
+      local_description_ = sdesc;
+    }
+    return true;
+  }
+
+  // Returns the application-level description given by the other client.
+  // If we are the initiator, this will be NULL until we receive an accept.
+  const SessionDescription* remote_description() const {
+    return remote_description_;
+  }
+  // Takes ownership of SessionDescription*
+  bool set_remote_description(const SessionDescription* sdesc) {
+    if (sdesc != remote_description_) {
+      delete remote_description_;
+      remote_description_ = sdesc;
+    }
+    return true;
+  }
+
+  // When we receive an initiate, we create a session in the
+  // RECEIVEDINITIATE state and respond by accepting or rejecting.
+  // Takes ownership of session description.
+  virtual bool Accept(const SessionDescription* sdesc) = 0;
+  virtual bool Reject(const std::string& reason) = 0;
+  bool Terminate() {
+    return TerminateWithReason(STR_TERMINATE_SUCCESS);
+  }
+  virtual bool TerminateWithReason(const std::string& reason) = 0;
+
+  // The worker thread used by the session manager
+  virtual talk_base::Thread *worker_thread() = 0;
+
+  talk_base::Thread *signaling_thread() {
+    return signaling_thread_;
+  }
+
+  // Returns the JID of this client.
+  const std::string& local_name() const { return local_name_; }
+
+  // Returns the JID of the other peer in this session.
+  const std::string& remote_name() const { return remote_name_; }
+
+  // Set the JID of the other peer in this session.
+  // Typically the remote_name_ is set when the session is initiated.
+  // However, sometimes (e.g when a proxy is used) the peer name is
+  // known after the BaseSession has been initiated and it must be updated
+  // explicitly.
+  void set_remote_name(const std::string& name) { remote_name_ = name; }
+
+  const std::string& id() const { return sid_; }
+
+ protected:
+  State state_;
+  Error error_;
+  const SessionDescription* local_description_;
+  const SessionDescription* remote_description_;
+  std::string sid_;
+  // We don't use buzz::Jid because changing to buzz:Jid here has a
+  // cascading effect that requires an enormous number places to
+  // change to buzz::Jid as well.
+  std::string local_name_;
+  std::string remote_name_;
+  talk_base::Thread *signaling_thread_;
+};
+
+// A specific Session created by the SessionManager, using XMPP for protocol.
+class Session : public BaseSession {
+ public:
+  // Returns the manager that created and owns this session.
+  SessionManager* session_manager() const { return session_manager_; }
+
+  // the worker thread used by the session manager
+  talk_base::Thread *worker_thread() {
+    return session_manager_->worker_thread();
+  }
+
+  // Returns the XML namespace identifying the type of this session.
+  const std::string& content_type() const { return content_type_; }
+
+  // Returns the client that is handling the application data of this session.
+  SessionClient* client() const { return client_; }
+
+  SignalingProtocol current_protocol() const { return current_protocol_; }
+
+  void set_current_protocol(SignalingProtocol protocol) {
+    current_protocol_ = protocol;
+  }
+
+  // Indicates whether we initiated this session.
+  bool initiator() const { return initiator_; }
+
+  const SessionDescription* initiator_description() const {
+    if (initiator_) {
+      return local_description_;
+    } else {
+      return remote_description_;
+    }
+  }
+
+  // Fired whenever we receive a terminate message along with a reason
+  sigslot::signal2<Session*, const std::string&> SignalReceivedTerminateReason;
+
+  void set_allow_local_ips(bool allow);
+
+  // Returns the transport that has been negotiated or NULL if
+  // negotiation is still in progress.
+  Transport* GetTransport(const std::string& content_name);
+
+  // Takes ownership of session description.
+  // TODO: Add an error argument to pass back to the caller.
+  bool Initiate(const std::string& to,
+                const SessionDescription* sdesc);
+
+  // When we receive an initiate, we create a session in the
+  // RECEIVEDINITIATE state and respond by accepting or rejecting.
+  // Takes ownership of session description.
+  // TODO: Add an error argument to pass back to the caller.
+  virtual bool Accept(const SessionDescription* sdesc);
+  virtual bool Reject(const std::string& reason);
+  virtual bool TerminateWithReason(const std::string& reason);
+
+  // The two clients in the session may also send one another arbitrary XML
+  // messages, which are called "info" messages.  Both of these functions take
+  // ownership of the XmlElements and delete them when done.
+  bool SendInfoMessage(const XmlElements& elems);
+  sigslot::signal2<Session*, const XmlElements&> SignalInfoMessage;
+
+  // Maps passed to serialization functions.
+  TransportParserMap GetTransportParsers();
+  ContentParserMap GetContentParsers();
+
+  // Creates a new channel with the given names.  This method may be called
+  // immediately after creating the session.  However, the actual
+  // implementation may not be fixed until transport negotiation completes.
+  virtual TransportChannel* CreateChannel(const std::string& content_name,
+                                          const std::string& channel_name);
+
+  // Returns the channel with the given names.
+  virtual TransportChannel* GetChannel(const std::string& content_name,
+                                       const std::string& channel_name);
+
+  // Destroys the channel with the given names.
+  virtual void DestroyChannel(const std::string& content_name,
+                              const std::string& channel_name);
+
+  // Updates the error state, signaling if necessary.
+  virtual void SetError(Error error);
+
+  // Handles messages posted to us.
+  virtual void OnMessage(talk_base::Message *pmsg);
+
+  // Fired when notification of media sources is received from the server.
+  // Passes a map whose keys are strings containing nick names for users
+  // in the session and whose values contain the SSRCs for each user.
+  sigslot::signal1<const StringToMediaSourcesMap&> SignalMediaSources;
+
+  // Sets the video streams to receive from the server.
+  bool SetVideoView(const VideoViewRequestVector& view_requests);
+
+ private:
+  // Creates or destroys a session.  (These are called only SessionManager.)
+  Session(SessionManager *session_manager,
+          const std::string& local_name, const std::string& initiator_name,
+          const std::string& sid, const std::string& content_type,
+          SessionClient* client);
+  ~Session();
+
+  // Get a TransportProxy by content_name or transport. NULL if not found.
+  TransportProxy* GetTransportProxy(const std::string& content_name);
+  TransportProxy* GetTransportProxy(const Transport* transport);
+  TransportProxy* GetFirstTransportProxy();
+  // TransportProxy is owned by session.  Return proxy just for convenience.
+  TransportProxy* GetOrCreateTransportProxy(const std::string& content_name);
+  // For each transport info, create a transport proxy.  Can fail for
+  // incompatible transport types.
+  bool CreateTransportProxies(const TransportInfos& tinfos,
+                              SessionError* error);
+  void SpeculativelyConnectAllTransportChannels();
+  bool OnRemoteCandidates(const TransportInfos& tinfos,
+                          ParseError* error);
+  // Returns a TransportInfo without candidates for each content name.
+  // Uses the transport_type_ of the session.
+  TransportInfos GetEmptyTransportInfos(const ContentInfos& contents) const;
+
+  // Called when the first channel of a transport begins connecting.  We use
+  // this to start a timer, to make sure that the connection completes in a
+  // reasonable amount of time.
+  void OnTransportConnecting(Transport* transport);
+
+  // Called when a transport changes its writable state.  We track this to make
+  // sure that the transport becomes writable within a reasonable amount of
+  // time.  If this does not occur, we signal an error.
+  void OnTransportWritable(Transport* transport);
+
+  // Called when a transport requests signaling.
+  void OnTransportRequestSignaling(Transport* transport);
+
+  // Called when a transport signals that it has a message to send.   Note that
+  // these messages are just the transport part of the stanza; they need to be
+  // wrapped in the appropriate session tags.
+  void OnTransportCandidatesReady(Transport* transport,
+                                  const Candidates& candidates);
+
+  // Called when a transport signals that it found an error in an incoming
+  // message.
+  void OnTransportSendError(Transport* transport,
+                            const buzz::XmlElement* stanza,
+                            const buzz::QName& name,
+                            const std::string& type,
+                            const std::string& text,
+                            const buzz::XmlElement* extra_info);
+
+  // Called when we notice that one of our local channels has no peer, so it
+  // should be destroyed.
+  void OnTransportChannelGone(Transport* transport, const std::string& name);
+
+  // When the session needs to send signaling messages, it beings by requesting
+  // signaling.  The client should handle this by calling OnSignalingReady once
+  // it is ready to send the messages.
+  // (These are called only by SessionManager.)
+  sigslot::signal1<Session*> SignalRequestSignaling;
+  void OnSignalingReady();
+
+  // Send various kinds of session messages.
+  bool SendInitiateMessage(const SessionDescription* sdesc,
+                           SessionError* error);
+  bool SendAcceptMessage(const SessionDescription* sdesc, SessionError* error);
+  bool SendRejectMessage(const std::string& reason, SessionError* error);
+  bool SendTerminateMessage(const std::string& reason, SessionError* error);
+  bool SendTransportInfoMessage(const TransportInfo& tinfo,
+                                SessionError* error);
+  bool SendViewMessage(const SessionView& view, SessionError* error);
+  bool ResendAllTransportInfoMessages(SessionError* error);
+
+  // Both versions of SendMessage send a message of the given type to
+  // the other client.  Can pass either a set of elements or an
+  // "action", which must have a WriteSessionAction method to go along
+  // with it.  Sending with an action supports sending a "hybrid"
+  // message.  Sending with elements must be sent as Jingle or Gingle.
+
+  // When passing elems, must be either Jingle or Gingle protocol.
+  // Takes ownership of action_elems.
+  bool SendMessage(ActionType type, const XmlElements& action_elems,
+                   SessionError* error);
+  // When passing an action, may be Hybrid protocol.
+  template <typename Action>
+  bool SendMessage(ActionType type, const Action& action,
+                   SessionError* error);
+
+  // Helper methods to write the session message stanza.
+  template <typename Action>
+  bool WriteActionMessage(ActionType type, const Action& action,
+                          buzz::XmlElement* stanza, WriteError* error);
+  template <typename Action>
+  bool WriteActionMessage(SignalingProtocol protocol,
+                          ActionType type, const Action& action,
+                          buzz::XmlElement* stanza, WriteError* error);
+
+  // Sending messages in hybrid form requires being able to write them
+  // on a per-protocol basis with a common method signature, which all
+  // of these have.
+  bool WriteSessionAction(SignalingProtocol protocol,
+                          const SessionInitiate& init,
+                          XmlElements* elems, WriteError* error);
+  bool WriteSessionAction(SignalingProtocol protocol,
+                          const TransportInfo& tinfo,
+                          XmlElements* elems, WriteError* error);
+  bool WriteSessionAction(SignalingProtocol protocol,
+                          const SessionTerminate& term,
+                          XmlElements* elems, WriteError* error);
+
+  // Sends a message back to the other client indicating that we have received
+  // and accepted their message.
+  void SendAcknowledgementMessage(const buzz::XmlElement* stanza);
+
+  // Once signaling is ready, the session will use this signal to request the
+  // sending of each message.  When messages are received by the other client,
+  // they should be handed to OnIncomingMessage.
+  // (These are called only by SessionManager.)
+  sigslot::signal2<Session *, const buzz::XmlElement*> SignalOutgoingMessage;
+  void OnIncomingMessage(const SessionMessage& msg);
+
+  void OnFailedSend(const buzz::XmlElement* orig_stanza,
+                    const buzz::XmlElement* error_stanza);
+
+  // Invoked when an error is found in an incoming message.  This is translated
+  // into the appropriate XMPP response by SessionManager.
+  sigslot::signal6<BaseSession*,
+                   const buzz::XmlElement*,
+                   const buzz::QName&,
+                   const std::string&,
+                   const std::string&,
+                   const buzz::XmlElement*> SignalErrorMessage;
+
+  // Handlers for the various types of messages.  These functions may take
+  // pointers to the whole stanza or to just the session element.
+  bool OnInitiateMessage(const SessionMessage& msg, MessageError* error);
+  bool OnAcceptMessage(const SessionMessage& msg, MessageError* error);
+  bool OnRejectMessage(const SessionMessage& msg, MessageError* error);
+  bool OnInfoMessage(const SessionMessage& msg);
+  bool OnTerminateMessage(const SessionMessage& msg, MessageError* error);
+  bool OnTransportInfoMessage(const SessionMessage& msg, MessageError* error);
+  bool OnTransportAcceptMessage(const SessionMessage& msg, MessageError* error);
+  bool OnNotifyMessage(const SessionMessage& msg, MessageError* error);
+  bool OnUpdateMessage(const SessionMessage& msg, MessageError* error);
+  bool OnRedirectError(const SessionRedirect& redirect, SessionError* error);
+
+  // Verifies that we are in the appropriate state to receive this message.
+  bool CheckState(State state, MessageError* error);
+
+  SessionManager *session_manager_;
+  bool initiator_;
+  std::string initiator_name_;
+  std::string content_type_;
+  SessionClient* client_;
+  std::string transport_type_;
+  TransportParser* transport_parser_;
+  // This is transport-specific but required so much by unit tests
+  // that it's much easier to put it here.
+  bool allow_local_ips_;
+  TransportMap transports_;
+  // Keeps track of what protocol we are speaking.
+  SignalingProtocol current_protocol_;
+
+  friend class SessionManager;  // For access to constructor, destructor,
+                                // and signaling related methods.
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_SESSION_H_
diff --git a/talk/p2p/base/sessionclient.h b/talk/p2p/base/sessionclient.h
new file mode 100644
index 0000000..d6604a9
--- /dev/null
+++ b/talk/p2p/base/sessionclient.h
@@ -0,0 +1,85 @@
+/*
+ * 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_P2P_BASE_SESSIONCLIENT_H_
+#define TALK_P2P_BASE_SESSIONCLIENT_H_
+
+namespace buzz {
+class XmlElement;
+}
+
+namespace cricket {
+
+struct ParseError;
+class Session;
+class SessionDescription;
+
+class ContentParser {
+ public:
+  virtual bool ParseContent(SignalingProtocol protocol,
+                            const buzz::XmlElement* elem,
+                            const ContentDescription** content,
+                            ParseError* error) = 0;
+  virtual bool WriteContent(SignalingProtocol protocol,
+                            const ContentDescription* content,
+                            buzz::XmlElement** elem,
+                            WriteError* error) = 0;
+  virtual ~ContentParser() {}
+};
+
+// A SessionClient exists in 1-1 relation with each session.  The implementor
+// of this interface is the one that understands *what* the two sides are
+// trying to send to one another.  The lower-level layers only know how to send
+// data; they do not know what is being sent.
+class SessionClient : public ContentParser {
+ public:
+  // Notifies the client of the creation / destruction of sessions of this type.
+  //
+  // IMPORTANT: The SessionClient, in its handling of OnSessionCreate, must
+  // create whatever channels are indicate in the description.  This is because
+  // the remote client may already be attempting to connect those channels. If
+  // we do not create our channel right away, then connection may fail or be
+  // delayed.
+  virtual void OnSessionCreate(Session* session, bool received_initiate) = 0;
+  virtual void OnSessionDestroy(Session* session) = 0;
+
+  virtual bool ParseContent(SignalingProtocol protocol,
+                            const buzz::XmlElement* elem,
+                            const ContentDescription** content,
+                            ParseError* error) = 0;
+  virtual bool WriteContent(SignalingProtocol protocol,
+                            const ContentDescription* content,
+                            buzz::XmlElement** elem,
+                            WriteError* error) = 0;
+ protected:
+  // The SessionClient interface explicitly does not include destructor
+  virtual ~SessionClient() { }
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_SESSIONCLIENT_H_
diff --git a/talk/p2p/base/sessiondescription.cc b/talk/p2p/base/sessiondescription.cc
new file mode 100644
index 0000000..872358e
--- /dev/null
+++ b/talk/p2p/base/sessiondescription.cc
@@ -0,0 +1,72 @@
+/*
+ * 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 "talk/p2p/base/sessiondescription.h"
+
+#include "talk/xmllite/xmlelement.h"
+
+namespace cricket {
+
+const ContentInfo* FindContentInfoByName(
+    const ContentInfos& contents, const std::string& name) {
+  for (ContentInfos::const_iterator content = contents.begin();
+       content != contents.end(); content++) {
+    if (content->name == name) {
+      return &(*content);
+    }
+  }
+  return NULL;
+}
+
+const ContentInfo* FindContentInfoByType(
+    const ContentInfos& contents, const std::string& type) {
+  for (ContentInfos::const_iterator content = contents.begin();
+       content != contents.end(); content++) {
+    if (content->type == type) {
+      return &(*content);
+    }
+  }
+  return NULL;
+}
+
+const ContentInfo* SessionDescription::GetContentByName(
+    const std::string& name) const {
+  return FindContentInfoByName(contents_, name);
+}
+
+const ContentInfo* SessionDescription::FirstContentByType(
+    const std::string& type) const {
+  return FindContentInfoByType(contents_, type);
+}
+
+void SessionDescription::AddContent(const std::string& name,
+                                    const std::string& type,
+                                    const ContentDescription* description) {
+  contents_.push_back(ContentInfo(name, type, description));
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/sessiondescription.h b/talk/p2p/base/sessiondescription.h
new file mode 100644
index 0000000..fe575fa
--- /dev/null
+++ b/talk/p2p/base/sessiondescription.h
@@ -0,0 +1,110 @@
+/*
+ * 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_P2P_BASE_SESSIONDESCRIPTION_H_
+#define TALK_P2P_BASE_SESSIONDESCRIPTION_H_
+
+#include <string>
+#include <vector>
+
+namespace cricket {
+
+// Describes a session content. Individual content types inherit from
+// this class.  Analagous to a <jingle><content><description> or
+// <session><description>.
+class ContentDescription {
+ public:
+  virtual ~ContentDescription() {}
+};
+
+// Analagous to a <jingle><content> or <session><description>.
+// name = name of <content name="...">
+// type = xmlns of <content>
+struct ContentInfo {
+  ContentInfo() : description(NULL) {}
+  ContentInfo(const std::string& name,
+              const std::string& type,
+              const ContentDescription* description) :
+      name(name), type(type), description(description) {}
+  std::string name;
+  std::string type;
+  const ContentDescription* description;
+};
+
+typedef std::vector<ContentInfo> ContentInfos;
+const ContentInfo* FindContentInfoByName(
+    const ContentInfos& contents, const std::string& name);
+const ContentInfo* FindContentInfoByType(
+    const ContentInfos& contents, const std::string& type);
+
+// Describes a collection of contents, each with its own name and
+// type.  Analgous to a <jingle> or <session> stanza.  Assumes that
+// contents are unique be name, but doesn't enforce that.
+class SessionDescription {
+ public:
+  SessionDescription() {}
+  explicit SessionDescription(const ContentInfos& contents) :
+      contents_(contents) {}
+  const ContentInfo* GetContentByName(const std::string& name) const;
+  const ContentInfo* FirstContentByType(const std::string& type) const;
+  // Takes ownership of ContentDescription*.
+  void AddContent(const std::string& name,
+                  const std::string& type,
+                  const ContentDescription* description);
+  // TODO: Implement RemoveContent when it's needed for
+  // content-remove Jingle messages.
+  // void RemoveContent(const std::string& name);
+  const ContentInfos& contents() const { return contents_; }
+
+  ~SessionDescription() {
+    for (ContentInfos::iterator content = contents_.begin();
+         content != contents_.end(); content++) {
+      delete content->description;
+    }
+  }
+
+ private:
+  ContentInfos contents_;
+};
+
+// Indicates whether a ContentDescription was an offer or an answer, as
+// described in http://www.ietf.org/rfc/rfc3264.txt. CA_UPDATE
+// indicates a jingle update message which contains a subset of a full
+// session description
+enum ContentAction {
+  CA_OFFER, CA_ANSWER, CA_UPDATE
+};
+
+// Indicates whether a ContentDescription was sent by the local client
+// or received from the remote client.
+enum ContentSource {
+  CS_LOCAL, CS_REMOTE
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_SESSIONDESCRIPTION_H_
diff --git a/talk/p2p/base/sessionid.h b/talk/p2p/base/sessionid.h
new file mode 100644
index 0000000..6942942
--- /dev/null
+++ b/talk/p2p/base/sessionid.h
@@ -0,0 +1,37 @@
+/*
+ * 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_P2P_BASE_SESSIONID_H_
+#define TALK_P2P_BASE_SESSIONID_H_
+
+// TODO: Remove this file.
+
+namespace cricket {
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_SESSIONID_H_
diff --git a/talk/p2p/base/sessionmanager.cc b/talk/p2p/base/sessionmanager.cc
new file mode 100644
index 0000000..61a4c4a
--- /dev/null
+++ b/talk/p2p/base/sessionmanager.cc
@@ -0,0 +1,296 @@
+/*
+ * 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/p2p/base/sessionmanager.h"
+
+#include "talk/base/common.h"
+#include "talk/base/helpers.h"
+#include "talk/base/stringencode.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/session.h"
+#include "talk/p2p/base/sessionmessages.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/jid.h"
+
+namespace cricket {
+
+SessionManager::SessionManager(PortAllocator *allocator,
+                               talk_base::Thread *worker) {
+  allocator_ = allocator;
+  signaling_thread_ = talk_base::Thread::Current();
+  if (worker == NULL) {
+    worker_thread_ = talk_base::Thread::Current();
+  } else {
+    worker_thread_ = worker;
+  }
+  timeout_ = 50;
+}
+
+SessionManager::~SessionManager() {
+  // Note: Session::Terminate occurs asynchronously, so it's too late to
+  // delete them now.  They better be all gone.
+  ASSERT(session_map_.empty());
+  // TerminateAll();
+}
+
+void SessionManager::AddClient(const std::string& content_type,
+                               SessionClient* client) {
+  ASSERT(client_map_.find(content_type) == client_map_.end());
+  client_map_[content_type] = client;
+}
+
+void SessionManager::RemoveClient(const std::string& content_type) {
+  ClientMap::iterator iter = client_map_.find(content_type);
+  ASSERT(iter != client_map_.end());
+  client_map_.erase(iter);
+}
+
+SessionClient* SessionManager::GetClient(const std::string& content_type) {
+  ClientMap::iterator iter = client_map_.find(content_type);
+  return (iter != client_map_.end()) ? iter->second : NULL;
+}
+
+Session* SessionManager::CreateSession(const std::string& local_name,
+                                       const std::string& content_type) {
+  return CreateSession(local_name, local_name,
+                       talk_base::ToString(talk_base::CreateRandomId()),
+                       content_type, false);
+}
+
+Session* SessionManager::CreateSession(
+    const std::string& local_name, const std::string& initiator_name,
+    const std::string& sid, const std::string& content_type,
+    bool received_initiate) {
+  SessionClient* client = GetClient(content_type);
+  ASSERT(client != NULL);
+
+  Session* session = new Session(this, local_name, initiator_name,
+                                 sid, content_type, client);
+  session_map_[session->id()] = session;
+  session->SignalRequestSignaling.connect(
+      this, &SessionManager::OnRequestSignaling);
+  session->SignalOutgoingMessage.connect(
+      this, &SessionManager::OnOutgoingMessage);
+  session->SignalErrorMessage.connect(this, &SessionManager::OnErrorMessage);
+  SignalSessionCreate(session, received_initiate);
+  session->client()->OnSessionCreate(session, received_initiate);
+  return session;
+}
+
+void SessionManager::DestroySession(Session* session) {
+  if (session != NULL) {
+    SessionMap::iterator it = session_map_.find(session->id());
+    if (it != session_map_.end()) {
+      SignalSessionDestroy(session);
+      session->client()->OnSessionDestroy(session);
+      session_map_.erase(it);
+      delete session;
+    }
+  }
+}
+
+Session* SessionManager::GetSession(const std::string& sid) {
+  SessionMap::iterator it = session_map_.find(sid);
+  if (it != session_map_.end())
+    return it->second;
+  return NULL;
+}
+
+void SessionManager::TerminateAll() {
+  while (session_map_.begin() != session_map_.end()) {
+    Session* session = session_map_.begin()->second;
+    session->Terminate();
+  }
+}
+
+bool SessionManager::IsSessionMessage(const buzz::XmlElement* stanza) {
+  return cricket::IsSessionMessage(stanza);
+}
+
+Session* SessionManager::FindSession(const std::string& sid,
+                                     const std::string& remote_name) {
+  SessionMap::iterator iter = session_map_.find(sid);
+  if (iter == session_map_.end())
+    return NULL;
+
+  Session* session = iter->second;
+  if (buzz::Jid(remote_name) != buzz::Jid(session->remote_name()))
+    return NULL;
+
+  return session;
+}
+
+void SessionManager::OnIncomingMessage(const buzz::XmlElement* stanza) {
+  SessionMessage msg;
+  ParseError error;
+
+  if (!ParseSessionMessage(stanza, &msg, &error)) {
+    SendErrorMessage(stanza, buzz::QN_STANZA_BAD_REQUEST, "modify",
+                     error.text, NULL);
+    return;
+  }
+
+  Session* session = FindSession(msg.sid, msg.from);
+  if (session) {
+    session->OnIncomingMessage(msg);
+    return;
+  }
+  if (msg.type != ACTION_SESSION_INITIATE) {
+    SendErrorMessage(stanza, buzz::QN_STANZA_BAD_REQUEST, "modify",
+                     "unknown session", NULL);
+    return;
+  }
+
+  std::string content_type;
+  if (!ParseContentType(msg.protocol, msg.action_elem,
+                        &content_type, &error)) {
+    SendErrorMessage(stanza, buzz::QN_STANZA_BAD_REQUEST, "modify",
+                     error.text, NULL);
+    return;
+  }
+
+  if (!GetClient(content_type)) {
+    SendErrorMessage(stanza, buzz::QN_STANZA_BAD_REQUEST, "modify",
+                     "unknown content type: " + content_type, NULL);
+    return;
+  }
+
+  session = CreateSession(msg.to, msg.initiator, msg.sid,
+                          content_type, true);
+  session->OnIncomingMessage(msg);
+}
+
+void SessionManager::OnIncomingResponse(const buzz::XmlElement* orig_stanza,
+    const buzz::XmlElement* response_stanza) {
+  // We don't do anything with the response now.  If we need to we can forward
+  // it to the session.
+  return;
+}
+
+void SessionManager::OnFailedSend(const buzz::XmlElement* orig_stanza,
+                                  const buzz::XmlElement* error_stanza) {
+  SessionMessage msg;
+  ParseError error;
+  if (!ParseSessionMessage(orig_stanza, &msg, &error)) {
+    return;  // TODO: log somewhere?
+  }
+
+  Session* session = FindSession(msg.sid, msg.to);
+  if (session) {
+    talk_base::scoped_ptr<buzz::XmlElement> synthetic_error;
+    if (!error_stanza) {
+      // A failed send is semantically equivalent to an error response, so we
+      // can just turn the former into the latter.
+      synthetic_error.reset(
+        CreateErrorMessage(orig_stanza, buzz::QN_STANZA_ITEM_NOT_FOUND,
+                           "cancel", "Recipient did not respond", NULL));
+      error_stanza = synthetic_error.get();
+    }
+
+    session->OnFailedSend(orig_stanza, error_stanza);
+  }
+}
+
+void SessionManager::SendErrorMessage(const buzz::XmlElement* stanza,
+                                      const buzz::QName& name,
+                                      const std::string& type,
+                                      const std::string& text,
+                                      const buzz::XmlElement* extra_info) {
+  talk_base::scoped_ptr<buzz::XmlElement> msg(
+      CreateErrorMessage(stanza, name, type, text, extra_info));
+  SignalOutgoingMessage(this, msg.get());
+}
+
+buzz::XmlElement* SessionManager::CreateErrorMessage(
+    const buzz::XmlElement* stanza,
+    const buzz::QName& name,
+    const std::string& type,
+    const std::string& text,
+    const buzz::XmlElement* extra_info) {
+  buzz::XmlElement* iq = new buzz::XmlElement(buzz::QN_IQ);
+  iq->SetAttr(buzz::QN_TO, stanza->Attr(buzz::QN_FROM));
+  iq->SetAttr(buzz::QN_ID, stanza->Attr(buzz::QN_ID));
+  iq->SetAttr(buzz::QN_TYPE, "error");
+
+  CopyXmlChildren(stanza, iq);
+
+  buzz::XmlElement* error = new buzz::XmlElement(buzz::QN_ERROR);
+  error->SetAttr(buzz::QN_TYPE, type);
+  iq->AddElement(error);
+
+  // If the error name is not in the standard namespace, we have to first add
+  // some error from that namespace.
+  if (name.Namespace() != buzz::NS_STANZA) {
+     error->AddElement(
+         new buzz::XmlElement(buzz::QN_STANZA_UNDEFINED_CONDITION));
+  }
+  error->AddElement(new buzz::XmlElement(name));
+
+  if (extra_info)
+    error->AddElement(new buzz::XmlElement(*extra_info));
+
+  if (text.size() > 0) {
+    // It's okay to always use English here.  This text is for debugging
+    // purposes only.
+    buzz::XmlElement* text_elem = new buzz::XmlElement(buzz::QN_STANZA_TEXT);
+    text_elem->SetAttr(buzz::QN_XML_LANG, "en");
+    text_elem->SetBodyText(text);
+    error->AddElement(text_elem);
+  }
+
+  // TODO: Should we include error codes as well for SIP compatibility?
+
+  return iq;
+}
+
+void SessionManager::OnOutgoingMessage(Session* session,
+                                       const buzz::XmlElement* stanza) {
+  SignalOutgoingMessage(this, stanza);
+}
+
+void SessionManager::OnErrorMessage(BaseSession* session,
+                                    const buzz::XmlElement* stanza,
+                                    const buzz::QName& name,
+                                    const std::string& type,
+                                    const std::string& text,
+                                    const buzz::XmlElement* extra_info) {
+  SendErrorMessage(stanza, name, type, text, extra_info);
+}
+
+void SessionManager::OnSignalingReady() {
+  for (SessionMap::iterator it = session_map_.begin();
+      it != session_map_.end();
+      ++it) {
+    it->second->OnSignalingReady();
+  }
+}
+
+void SessionManager::OnRequestSignaling(Session* session) {
+  SignalRequestSignaling();
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/sessionmanager.h b/talk/p2p/base/sessionmanager.h
new file mode 100644
index 0000000..9bc1019
--- /dev/null
+++ b/talk/p2p/base/sessionmanager.h
@@ -0,0 +1,187 @@
+/*
+ * 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_P2P_BASE_SESSIONMANAGER_H_
+#define TALK_P2P_BASE_SESSIONMANAGER_H_
+
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "talk/base/sigslot.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/portallocator.h"
+
+namespace buzz {
+class QName;
+class XmlElement;
+}
+
+namespace cricket {
+
+class Session;
+class BaseSession;
+class SessionClient;
+
+// SessionManager manages session instances
+
+class SessionManager : public sigslot::has_slots<> {
+ public:
+  SessionManager(PortAllocator *allocator,
+                 talk_base::Thread *worker_thread = NULL);
+  virtual ~SessionManager();
+
+  PortAllocator *port_allocator() const { return allocator_; }
+  talk_base::Thread *worker_thread() const { return worker_thread_; }
+  talk_base::Thread *signaling_thread() const { return signaling_thread_; }
+
+  int session_timeout() const { return timeout_; }
+  void set_session_timeout(int timeout) { timeout_ = timeout; }
+
+  // Registers support for the given client.  If we receive an initiate
+  // describing a session of the given type, we will automatically create a
+  // Session object and notify this client.  The client may then accept or
+  // reject the session.
+  void AddClient(const std::string& content_type, SessionClient* client);
+  void RemoveClient(const std::string& content_type);
+  SessionClient* GetClient(const std::string& content_type);
+
+  // Creates a new session.  The given name is the JID of the client on whose
+  // behalf we initiate the session.
+  Session *CreateSession(const std::string& local_name,
+                         const std::string& content_type);
+
+  // Destroys the given session.
+  void DestroySession(Session *session);
+
+  // Returns the session with the given ID or NULL if none exists.
+  Session *GetSession(const std::string& sid);
+
+  // Terminates all of the sessions created by this manager.
+  void TerminateAll();
+
+  // These are signaled whenever the set of existing sessions changes.
+  sigslot::signal2<Session *, bool> SignalSessionCreate;
+  sigslot::signal1<Session *> SignalSessionDestroy;
+
+  // Determines whether the given stanza is intended for some session.
+  bool IsSessionMessage(const buzz::XmlElement* stanza);
+
+  // Given a sid, initiator, and remote_name, this finds the matching Session
+  Session* FindSession(const std::string& sid,
+                       const std::string& remote_name);
+
+  // Called when we receive a stanza for which IsSessionMessage is true.
+  void OnIncomingMessage(const buzz::XmlElement* stanza);
+
+  // Called when we get a response to a message that we sent.
+  void OnIncomingResponse(const buzz::XmlElement* orig_stanza,
+                          const buzz::XmlElement* response_stanza);
+
+  // Called if an attempted to send times out or an error is returned.  In the
+  // timeout case error_stanza will be NULL
+  void OnFailedSend(const buzz::XmlElement* orig_stanza,
+                    const buzz::XmlElement* error_stanza);
+
+  // Signalled each time a session generates a signaling message to send.
+  // Also signalled on errors, but with a NULL session.
+  sigslot::signal2<SessionManager*,
+                   const buzz::XmlElement*> SignalOutgoingMessage;
+
+  // Signaled before sessions try to send certain signaling messages.  The
+  // client should call OnSignalingReady once it is safe to send them.  These
+  // steps are taken so that we don't send signaling messages trying to
+  // re-establish the connectivity of a session when the client cannot send
+  // the messages (and would probably just drop them on the floor).
+  //
+  // Note: you can connect this directly to OnSignalingReady(), if a signalling
+  // check is not supported.
+  sigslot::signal0<> SignalRequestSignaling;
+  void OnSignalingReady();
+
+ private:
+  typedef std::map<std::string, Session*> SessionMap;
+  typedef std::map<std::string, SessionClient*> ClientMap;
+
+  PortAllocator *allocator_;
+  talk_base::Thread *signaling_thread_;
+  talk_base::Thread *worker_thread_;
+  int timeout_;
+  SessionMap session_map_;
+  ClientMap client_map_;
+
+  // Helper function for CreateSession.  This is also invoked when we receive
+  // a message attempting to initiate a session with this client.
+  Session *CreateSession(const std::string& local_name,
+                         const std::string& initiator,
+                         const std::string& sid,
+                         const std::string& content_type,
+                         bool received_initiate);
+
+  // Attempts to find a registered session type whose description appears as
+  // a child of the session element.  Such a child should be present indicating
+  // the application they hope to initiate.
+  std::string FindClient(const buzz::XmlElement* session);
+
+  // Sends a message back to the other client indicating that we found an error
+  // in the stanza they sent.  name identifies the error, type is one of the
+  // standard XMPP types (cancel, continue, modify, auth, wait), and text is a
+  // description for debugging purposes.
+  void SendErrorMessage(const buzz::XmlElement* stanza,
+                        const buzz::QName& name,
+                        const std::string& type,
+                        const std::string& text,
+                        const buzz::XmlElement* extra_info);
+
+  // Creates and returns an error message from the given components.  The
+  // caller is responsible for deleting this.
+  buzz::XmlElement* CreateErrorMessage(
+      const buzz::XmlElement* stanza,
+      const buzz::QName& name,
+      const std::string& type,
+      const std::string& text,
+      const buzz::XmlElement* extra_info);
+
+  // Called each time a session requests signaling.
+  void OnRequestSignaling(Session* session);
+
+  // Called each time a session has an outgoing message.
+  void OnOutgoingMessage(Session* session, const buzz::XmlElement* stanza);
+
+  // Called each time a session has an error to send.
+  void OnErrorMessage(BaseSession* session,
+                      const buzz::XmlElement* stanza,
+                      const buzz::QName& name,
+                      const std::string& type,
+                      const std::string& text,
+                      const buzz::XmlElement* extra_info);
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_SESSIONMANAGER_H_
diff --git a/talk/p2p/base/sessionmessages.cc b/talk/p2p/base/sessionmessages.cc
new file mode 100644
index 0000000..d61b666
--- /dev/null
+++ b/talk/p2p/base/sessionmessages.cc
@@ -0,0 +1,970 @@
+/*
+ * 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 <stdio.h>
+#include <string>
+#include "talk/p2p/base/sessionmessages.h"
+
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stringutils.h"
+#include "talk/xmllite/xmlconstants.h"
+#include "talk/xmpp/constants.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/p2ptransport.h"
+#include "talk/p2p/base/parsing.h"
+#include "talk/p2p/base/sessionclient.h"
+#include "talk/p2p/base/sessiondescription.h"
+#include "talk/p2p/base/transport.h"
+#include "talk/xmllite/xmlconstants.h"
+
+namespace cricket {
+
+ActionType ToActionType(const std::string& type) {
+  if (type == GINGLE_ACTION_INITIATE)
+    return ACTION_SESSION_INITIATE;
+  if (type == GINGLE_ACTION_INFO)
+    return ACTION_SESSION_INFO;
+  if (type == GINGLE_ACTION_ACCEPT)
+    return ACTION_SESSION_ACCEPT;
+  if (type == GINGLE_ACTION_REJECT)
+    return ACTION_SESSION_REJECT;
+  if (type == GINGLE_ACTION_TERMINATE)
+    return ACTION_SESSION_TERMINATE;
+  if (type == GINGLE_ACTION_CANDIDATES)
+    return ACTION_TRANSPORT_INFO;
+  if (type == JINGLE_ACTION_SESSION_INITIATE)
+    return ACTION_SESSION_INITIATE;
+  if (type == JINGLE_ACTION_TRANSPORT_INFO)
+    return ACTION_TRANSPORT_INFO;
+  if (type == JINGLE_ACTION_TRANSPORT_ACCEPT)
+    return ACTION_TRANSPORT_ACCEPT;
+  if (type == JINGLE_ACTION_SESSION_INFO)
+    return ACTION_SESSION_INFO;
+  if (type == JINGLE_ACTION_SESSION_ACCEPT)
+    return ACTION_SESSION_ACCEPT;
+  if (type == JINGLE_ACTION_SESSION_TERMINATE)
+    return ACTION_SESSION_TERMINATE;
+  if (type == JINGLE_ACTION_TRANSPORT_INFO)
+    return ACTION_TRANSPORT_INFO;
+  if (type == JINGLE_ACTION_TRANSPORT_ACCEPT)
+    return ACTION_TRANSPORT_ACCEPT;
+  if (type == GINGLE_ACTION_NOTIFY)
+    return ACTION_NOTIFY;
+  if (type == GINGLE_ACTION_UPDATE)
+    return ACTION_UPDATE;
+
+  return ACTION_UNKNOWN;
+}
+
+std::string ToJingleString(ActionType type) {
+  switch (type) {
+    case ACTION_SESSION_INITIATE:
+      return JINGLE_ACTION_SESSION_INITIATE;
+    case ACTION_SESSION_INFO:
+      return JINGLE_ACTION_SESSION_INFO;
+    case ACTION_SESSION_ACCEPT:
+      return JINGLE_ACTION_SESSION_ACCEPT;
+    // Notice that reject and terminate both go to
+    // "session-terminate", but there is no "session-reject".
+    case ACTION_SESSION_REJECT:
+    case ACTION_SESSION_TERMINATE:
+      return JINGLE_ACTION_SESSION_TERMINATE;
+    case ACTION_TRANSPORT_INFO:
+      return JINGLE_ACTION_TRANSPORT_INFO;
+    case ACTION_TRANSPORT_ACCEPT:
+      return JINGLE_ACTION_TRANSPORT_ACCEPT;
+    default:
+      return "";
+  }
+}
+
+std::string ToGingleString(ActionType type) {
+  switch (type) {
+    case ACTION_SESSION_INITIATE:
+      return GINGLE_ACTION_INITIATE;
+    case ACTION_SESSION_INFO:
+      return GINGLE_ACTION_INFO;
+    case ACTION_SESSION_ACCEPT:
+      return GINGLE_ACTION_ACCEPT;
+    case ACTION_SESSION_REJECT:
+      return GINGLE_ACTION_REJECT;
+    case ACTION_SESSION_TERMINATE:
+      return GINGLE_ACTION_TERMINATE;
+    case ACTION_VIEW:
+      return GINGLE_ACTION_VIEW;
+    case ACTION_TRANSPORT_INFO:
+      return GINGLE_ACTION_CANDIDATES;
+    default:
+      return "";
+  }
+}
+
+
+bool IsJingleMessage(const buzz::XmlElement* stanza) {
+  const buzz::XmlElement* jingle = stanza->FirstNamed(QN_JINGLE);
+  if (jingle == NULL)
+    return false;
+
+  return (jingle->HasAttr(buzz::QN_ACTION) &&
+          (jingle->HasAttr(QN_SID)
+           // TODO: This works around a bug in old jingle
+           // clients that set QN_ID instead of QN_SID.  Once we know
+           // there are no clients which have this bug, we can remove
+           // this code.
+           || jingle->HasAttr(QN_ID)));
+}
+
+bool IsGingleMessage(const buzz::XmlElement* stanza) {
+  const buzz::XmlElement* session = stanza->FirstNamed(QN_GINGLE_SESSION);
+  if (session == NULL)
+    return false;
+
+  return (session->HasAttr(buzz::QN_TYPE) &&
+          session->HasAttr(buzz::QN_ID)   &&
+          session->HasAttr(QN_INITIATOR));
+}
+
+bool IsSessionMessage(const buzz::XmlElement* stanza) {
+  return (stanza->Name() == buzz::QN_IQ &&
+          stanza->Attr(buzz::QN_TYPE) == buzz::STR_SET &&
+          (IsJingleMessage(stanza) ||
+           IsGingleMessage(stanza)));
+}
+
+bool ParseGingleSessionMessage(const buzz::XmlElement* session,
+                               SessionMessage* msg,
+                               ParseError* error) {
+  msg->protocol = PROTOCOL_GINGLE;
+  std::string type_string = session->Attr(buzz::QN_TYPE);
+  msg->type = ToActionType(type_string);
+  msg->sid = session->Attr(buzz::QN_ID);
+  msg->initiator = session->Attr(QN_INITIATOR);
+  msg->action_elem = session;
+
+  if (msg->type == ACTION_UNKNOWN)
+    return BadParse("unknown action: " + type_string, error);
+
+  return true;
+}
+
+bool ParseJingleSessionMessage(const buzz::XmlElement* jingle,
+                               SessionMessage* msg,
+                               ParseError* error) {
+  msg->protocol = PROTOCOL_JINGLE;
+  std::string type_string = jingle->Attr(buzz::QN_ACTION);
+  msg->type = ToActionType(type_string);
+  msg->sid = jingle->Attr(QN_SID);
+  // TODO: This works around a bug in old jingle clients
+  // that set QN_ID instead of QN_SID.  Once we know there are no
+  // clients which have this bug, we can remove this code.
+  if (msg->sid.empty()) {
+    msg->sid = jingle->Attr(buzz::QN_ID);
+  }
+  msg->initiator = GetXmlAttr(jingle, QN_INITIATOR, buzz::STR_EMPTY);
+  msg->action_elem = jingle;
+
+  if (msg->type == ACTION_UNKNOWN)
+    return BadParse("unknown action: " + type_string, error);
+
+  return true;
+}
+
+bool ParseHybridSessionMessage(const buzz::XmlElement* jingle,
+                               SessionMessage* msg,
+                               ParseError* error) {
+  if (!ParseJingleSessionMessage(jingle, msg, error))
+    return false;
+  msg->protocol = PROTOCOL_HYBRID;
+
+  return true;
+}
+
+bool ParseSessionMessage(const buzz::XmlElement* stanza,
+                         SessionMessage* msg,
+                         ParseError* error) {
+  msg->id = stanza->Attr(buzz::QN_ID);
+  msg->from = stanza->Attr(buzz::QN_FROM);
+  msg->to = stanza->Attr(buzz::QN_TO);
+  msg->stanza = stanza;
+
+  const buzz::XmlElement* jingle = stanza->FirstNamed(QN_JINGLE);
+  const buzz::XmlElement* session = stanza->FirstNamed(QN_GINGLE_SESSION);
+  if (jingle && session)
+    return ParseHybridSessionMessage(jingle, msg, error);
+  if (jingle != NULL)
+    return ParseJingleSessionMessage(jingle, msg, error);
+  if (session != NULL)
+    return ParseGingleSessionMessage(session, msg, error);
+  return false;
+}
+
+buzz::XmlElement* WriteGingleAction(const SessionMessage& msg,
+                                    const XmlElements& action_elems) {
+  buzz::XmlElement* session = new buzz::XmlElement(QN_GINGLE_SESSION, true);
+  session->AddAttr(buzz::QN_TYPE, ToGingleString(msg.type));
+  session->AddAttr(buzz::QN_ID, msg.sid);
+  session->AddAttr(QN_INITIATOR, msg.initiator);
+  AddXmlChildren(session, action_elems);
+  return session;
+}
+
+buzz::XmlElement* WriteJingleAction(const SessionMessage& msg,
+                                    const XmlElements& action_elems) {
+  buzz::XmlElement* jingle = new buzz::XmlElement(QN_JINGLE, true);
+  jingle->AddAttr(buzz::QN_ACTION, ToJingleString(msg.type));
+  jingle->AddAttr(QN_SID, msg.sid);
+  // TODO: This works around a bug in old jingle clinets
+  // that expected QN_ID instead of QN_SID.  Once we know there are no
+  // clients which have this bug, we can remove this code.
+  jingle->AddAttr(QN_ID, msg.sid);
+  // TODO: Right now, the XMPP server rejects a jingle-only
+  // (non hybrid) message with "feature-not-implemented" if there is
+  // no initiator.  Fix the server, and then only set the initiator on
+  // session-initiate messages here.
+  jingle->AddAttr(QN_INITIATOR, msg.initiator);
+  AddXmlChildren(jingle, action_elems);
+  return jingle;
+}
+
+void WriteSessionMessage(const SessionMessage& msg,
+                         const XmlElements& action_elems,
+                         buzz::XmlElement* stanza) {
+  stanza->SetAttr(buzz::QN_TO, msg.to);
+  stanza->SetAttr(buzz::QN_TYPE, buzz::STR_SET);
+
+  if (msg.protocol == PROTOCOL_GINGLE) {
+    stanza->AddElement(WriteGingleAction(msg, action_elems));
+  } else {
+    stanza->AddElement(WriteJingleAction(msg, action_elems));
+  }
+}
+
+
+TransportParser* GetTransportParser(const TransportParserMap& trans_parsers,
+                                    const std::string& name) {
+  TransportParserMap::const_iterator map = trans_parsers.find(name);
+  if (map == trans_parsers.end()) {
+    return NULL;
+  } else {
+    return map->second;
+  }
+}
+
+bool ParseCandidates(SignalingProtocol protocol,
+                     const buzz::XmlElement* candidates_elem,
+                     const TransportParserMap& trans_parsers,
+                     const std::string& transport_type,
+                     Candidates* candidates,
+                     ParseError* error) {
+  TransportParser* trans_parser =
+      GetTransportParser(trans_parsers, transport_type);
+  if (trans_parser == NULL)
+    return BadParse("unknown transport type: " + transport_type, error);
+
+  return trans_parser->ParseCandidates(protocol, candidates_elem,
+                                       candidates, error);
+}
+
+bool ParseGingleTransportInfos(const buzz::XmlElement* action_elem,
+                               const ContentInfos& contents,
+                               const TransportParserMap& trans_parsers,
+                               TransportInfos* tinfos,
+                               ParseError* error) {
+  TransportInfo tinfo(CN_OTHER, NS_GINGLE_P2P, Candidates());
+  if (!ParseCandidates(PROTOCOL_GINGLE, action_elem,
+                       trans_parsers, NS_GINGLE_P2P,
+                       &tinfo.candidates, error))
+    return false;
+
+  bool has_audio = FindContentInfoByName(contents, CN_AUDIO) != NULL;
+  bool has_video = FindContentInfoByName(contents, CN_VIDEO) != NULL;
+
+  // If we don't have media, no need to separate the candidates.
+  if (!has_audio && !has_audio) {
+    tinfos->push_back(tinfo);
+    return true;
+  }
+
+  // If we have media, separate the candidates.  Create the
+  // TransportInfo here to avoid copying the candidates.
+  TransportInfo audio_tinfo(CN_AUDIO, NS_GINGLE_P2P, Candidates());
+  TransportInfo video_tinfo(CN_VIDEO, NS_GINGLE_P2P, Candidates());
+  for (Candidates::iterator cand = tinfo.candidates.begin();
+       cand != tinfo.candidates.end(); cand++) {
+    if (cand->name() == GINGLE_CANDIDATE_NAME_RTP ||
+        cand->name() == GINGLE_CANDIDATE_NAME_RTCP) {
+      audio_tinfo.candidates.push_back(*cand);
+    } else if (cand->name() == GINGLE_CANDIDATE_NAME_VIDEO_RTP ||
+               cand->name() == GINGLE_CANDIDATE_NAME_VIDEO_RTCP) {
+      video_tinfo.candidates.push_back(*cand);
+    }
+  }
+
+  if (has_audio) {
+    tinfos->push_back(audio_tinfo);
+  }
+
+  if (has_video) {
+    tinfos->push_back(video_tinfo);
+  }
+
+  return true;
+}
+
+bool ParseJingleTransportInfo(const buzz::XmlElement* trans_elem,
+                              const ContentInfo& content,
+                              const TransportParserMap& trans_parsers,
+                              TransportInfos* tinfos,
+                              ParseError* error) {
+  std::string transport_type = trans_elem->Name().Namespace();
+  TransportInfo tinfo(content.name, transport_type, Candidates());
+  if (!ParseCandidates(PROTOCOL_JINGLE, trans_elem,
+                       trans_parsers, transport_type,
+                       &tinfo.candidates, error))
+    return false;
+
+  tinfos->push_back(tinfo);
+  return true;
+}
+
+bool ParseJingleTransportInfos(const buzz::XmlElement* jingle,
+                               const ContentInfos& contents,
+                               const TransportParserMap trans_parsers,
+                               TransportInfos* tinfos,
+                               ParseError* error) {
+  for (const buzz::XmlElement* pair_elem
+           = jingle->FirstNamed(QN_JINGLE_CONTENT);
+       pair_elem != NULL;
+       pair_elem = pair_elem->NextNamed(QN_JINGLE_CONTENT)) {
+    std::string content_name;
+    if (!RequireXmlAttr(pair_elem, QN_JINGLE_CONTENT_NAME,
+                        &content_name, error))
+      return false;
+
+    const ContentInfo* content = FindContentInfoByName(contents, content_name);
+    if (!content)
+      return BadParse("Unknown content name: " + content_name, error);
+
+    const buzz::XmlElement* trans_elem;
+    if (!RequireXmlChild(pair_elem, LN_TRANSPORT, &trans_elem, error))
+      return false;
+
+    if (!ParseJingleTransportInfo(trans_elem, *content, trans_parsers,
+                                  tinfos, error))
+      return false;
+  }
+
+  return true;
+}
+
+buzz::XmlElement* NewTransportElement(const std::string& name) {
+  return new buzz::XmlElement(buzz::QName(true, name, LN_TRANSPORT), true);
+}
+
+bool WriteCandidates(SignalingProtocol protocol,
+                     const std::string& trans_type,
+                     const Candidates& candidates,
+                     const TransportParserMap& trans_parsers,
+                     XmlElements* elems,
+                     WriteError* error) {
+  TransportParser* trans_parser = GetTransportParser(trans_parsers, trans_type);
+  if (trans_parser == NULL)
+    return BadWrite("unknown transport type: " + trans_type, error);
+
+  return trans_parser->WriteCandidates(protocol, candidates, elems, error);
+}
+
+bool WriteGingleTransportInfos(const TransportInfos& tinfos,
+                               const TransportParserMap& trans_parsers,
+                               XmlElements* elems,
+                               WriteError* error) {
+  for (TransportInfos::const_iterator tinfo = tinfos.begin();
+       tinfo != tinfos.end(); ++tinfo) {
+    if (!WriteCandidates(PROTOCOL_GINGLE,
+                         tinfo->transport_type, tinfo->candidates,
+                         trans_parsers, elems, error))
+      return false;
+  }
+
+  return true;
+}
+
+bool WriteJingleTransportInfo(const TransportInfo& tinfo,
+                              const TransportParserMap& trans_parsers,
+                              XmlElements* elems,
+                              WriteError* error) {
+  XmlElements candidate_elems;
+  if (!WriteCandidates(PROTOCOL_JINGLE,
+                       tinfo.transport_type, tinfo.candidates, trans_parsers,
+                       &candidate_elems, error))
+    return false;
+
+  buzz::XmlElement* trans_elem = NewTransportElement(tinfo.transport_type);
+  AddXmlChildren(trans_elem, candidate_elems);
+  elems->push_back(trans_elem);
+  return true;
+}
+
+void WriteJingleContentPair(const std::string name,
+                            const XmlElements& pair_elems,
+                            XmlElements* elems) {
+  buzz::XmlElement* pair_elem = new buzz::XmlElement(QN_JINGLE_CONTENT);
+  pair_elem->SetAttr(QN_JINGLE_CONTENT_NAME, name);
+  pair_elem->SetAttr(QN_CREATOR, LN_INITIATOR);
+  AddXmlChildren(pair_elem, pair_elems);
+
+  elems->push_back(pair_elem);
+}
+
+bool WriteJingleTransportInfos(const TransportInfos& tinfos,
+                               const TransportParserMap& trans_parsers,
+                               XmlElements* elems,
+                               WriteError* error) {
+  for (TransportInfos::const_iterator tinfo = tinfos.begin();
+       tinfo != tinfos.end(); ++tinfo) {
+    XmlElements pair_elems;
+    if (!WriteJingleTransportInfo(*tinfo, trans_parsers,
+                                  &pair_elems, error))
+      return false;
+
+    WriteJingleContentPair(tinfo->content_name, pair_elems, elems);
+  }
+
+  return true;
+}
+
+ContentParser* GetContentParser(const ContentParserMap& content_parsers,
+                                const std::string& type) {
+  ContentParserMap::const_iterator map = content_parsers.find(type);
+  if (map == content_parsers.end()) {
+    return NULL;
+  } else {
+    return map->second;
+  }
+}
+
+bool ParseContentInfo(SignalingProtocol protocol,
+                      const std::string& name,
+                      const std::string& type,
+                      const buzz::XmlElement* elem,
+                      const ContentParserMap& parsers,
+                      ContentInfos* contents,
+                      ParseError* error) {
+  ContentParser* parser = GetContentParser(parsers, type);
+  if (parser == NULL)
+    return BadParse("unknown application content: " + type, error);
+
+  const ContentDescription* desc;
+  if (!parser->ParseContent(protocol, elem, &desc, error))
+    return false;
+
+  contents->push_back(ContentInfo(name, type, desc));
+  return true;
+}
+
+bool ParseContentType(const buzz::XmlElement* parent_elem,
+                      std::string* content_type,
+                      const buzz::XmlElement** content_elem,
+                      ParseError* error) {
+  if (!RequireXmlChild(parent_elem, LN_DESCRIPTION, content_elem, error))
+    return false;
+
+  *content_type = (*content_elem)->Name().Namespace();
+  return true;
+}
+
+bool ParseGingleContentInfos(const buzz::XmlElement* session,
+                             const ContentParserMap& content_parsers,
+                             ContentInfos* contents,
+                             ParseError* error) {
+  std::string content_type;
+  const buzz::XmlElement* content_elem;
+  if (!ParseContentType(session, &content_type, &content_elem, error))
+    return false;
+
+  if (content_type == NS_GINGLE_VIDEO) {
+    // A parser parsing audio or video content should look at the
+    // namespace and only parse the codecs relevant to that namespace.
+    // We use this to control which codecs get parsed: first audio,
+    // then video.
+    talk_base::scoped_ptr<buzz::XmlElement> audio_elem(
+        new buzz::XmlElement(QN_GINGLE_AUDIO_CONTENT));
+    CopyXmlChildren(content_elem, audio_elem.get());
+    if (!ParseContentInfo(PROTOCOL_GINGLE, CN_AUDIO, NS_JINGLE_RTP,
+                          audio_elem.get(), content_parsers,
+                          contents, error))
+      return false;
+
+    if (!ParseContentInfo(PROTOCOL_GINGLE, CN_VIDEO, NS_JINGLE_RTP,
+                          content_elem, content_parsers,
+                          contents, error))
+      return false;
+  } else if (content_type == NS_GINGLE_AUDIO) {
+    if (!ParseContentInfo(PROTOCOL_GINGLE, CN_AUDIO, NS_JINGLE_RTP,
+                          content_elem, content_parsers,
+                          contents, error))
+      return false;
+  } else {
+    if (!ParseContentInfo(PROTOCOL_GINGLE, CN_OTHER, content_type,
+                          content_elem, content_parsers,
+                          contents, error))
+      return false;
+  }
+  return true;
+}
+
+bool ParseJingleContentInfos(const buzz::XmlElement* jingle,
+                             const ContentParserMap& content_parsers,
+                             ContentInfos* contents,
+                             ParseError* error) {
+  for (const buzz::XmlElement* pair_elem
+           = jingle->FirstNamed(QN_JINGLE_CONTENT);
+       pair_elem != NULL;
+       pair_elem = pair_elem->NextNamed(QN_JINGLE_CONTENT)) {
+    std::string content_name;
+    if (!RequireXmlAttr(pair_elem, QN_JINGLE_CONTENT_NAME,
+                        &content_name, error))
+      return false;
+
+    std::string content_type;
+    const buzz::XmlElement* content_elem;
+    if (!ParseContentType(pair_elem, &content_type, &content_elem, error))
+      return false;
+
+    if (!ParseContentInfo(PROTOCOL_JINGLE, content_name, content_type,
+                          content_elem, content_parsers,
+                          contents, error))
+      return false;
+  }
+  return true;
+}
+
+buzz::XmlElement* WriteContentInfo(SignalingProtocol protocol,
+                                   const ContentInfo& content,
+                                   const ContentParserMap& parsers,
+                                   WriteError* error) {
+  ContentParser* parser = GetContentParser(parsers, content.type);
+  if (parser == NULL) {
+    BadWrite("unknown content type: " + content.type, error);
+    return NULL;
+  }
+
+  buzz::XmlElement* elem = NULL;
+  if (!parser->WriteContent(protocol, content.description, &elem, error))
+    return NULL;
+
+  return elem;
+}
+
+bool WriteGingleContentInfos(const ContentInfos& contents,
+                             const ContentParserMap& parsers,
+                             XmlElements* elems,
+                             WriteError* error) {
+  if (contents.size() == 1) {
+    buzz::XmlElement* elem = WriteContentInfo(
+        PROTOCOL_GINGLE, contents.front(), parsers, error);
+    if (!elem)
+      return false;
+
+    elems->push_back(elem);
+  } else if (contents.size() == 2 &&
+             contents.at(0).type == NS_JINGLE_RTP &&
+             contents.at(1).type == NS_JINGLE_RTP) {
+     // Special-case audio + video contents so that they are "merged"
+     // into one "video" content.
+    buzz::XmlElement* audio = WriteContentInfo(
+        PROTOCOL_GINGLE, contents.at(0), parsers, error);
+    if (!audio)
+      return false;
+
+    buzz::XmlElement* video = WriteContentInfo(
+        PROTOCOL_GINGLE, contents.at(1), parsers, error);
+    if (!video) {
+      delete audio;
+      return false;
+    }
+
+    CopyXmlChildren(audio, video);
+    elems->push_back(video);
+    delete audio;
+  } else {
+    return BadWrite("Gingle protocol may only have one content.", error);
+  }
+
+  return true;
+}
+
+const TransportInfo* GetTransportInfoByContentName(
+    const TransportInfos& tinfos, const std::string& content_name) {
+  for (TransportInfos::const_iterator tinfo = tinfos.begin();
+       tinfo != tinfos.end(); ++tinfo) {
+    if (content_name == tinfo->content_name) {
+      return &*tinfo;
+    }
+  }
+  return NULL;
+}
+
+bool WriteJingleContentPairs(const ContentInfos& contents,
+                             const ContentParserMap& content_parsers,
+                             const TransportInfos& tinfos,
+                             const TransportParserMap& trans_parsers,
+                             XmlElements* elems,
+                             WriteError* error) {
+  for (ContentInfos::const_iterator content = contents.begin();
+       content != contents.end(); ++content) {
+    const TransportInfo* tinfo =
+        GetTransportInfoByContentName(tinfos, content->name);
+    if (!tinfo)
+      return BadWrite("No transport for content: " + content->name, error);
+
+    XmlElements pair_elems;
+    buzz::XmlElement* elem = WriteContentInfo(
+        PROTOCOL_JINGLE, *content, content_parsers, error);
+    if (!elem)
+      return false;
+    pair_elems.push_back(elem);
+
+    if (!WriteJingleTransportInfo(*tinfo, trans_parsers,
+                                  &pair_elems, error))
+      return false;
+
+    WriteJingleContentPair(content->name, pair_elems, elems);
+  }
+  return true;
+}
+
+bool ParseContentType(SignalingProtocol protocol,
+                      const buzz::XmlElement* action_elem,
+                      std::string* content_type,
+                      ParseError* error) {
+  const buzz::XmlElement* content_elem;
+  if (protocol == PROTOCOL_GINGLE) {
+    if (!ParseContentType(action_elem, content_type, &content_elem, error))
+      return false;
+
+    // Internally, we only use NS_JINGLE_RTP.
+    if (*content_type == NS_GINGLE_AUDIO ||
+        *content_type == NS_GINGLE_VIDEO)
+      *content_type = NS_JINGLE_RTP;
+  } else {
+    const buzz::XmlElement* pair_elem
+        = action_elem->FirstNamed(QN_JINGLE_CONTENT);
+    if (pair_elem == NULL)
+      return BadParse("No contents found", error);
+
+    if (!ParseContentType(pair_elem, content_type, &content_elem, error))
+      return false;
+
+    // If there is more than one content type, return an error.
+    for (; pair_elem != NULL;
+         pair_elem = pair_elem->NextNamed(QN_JINGLE_CONTENT)) {
+      std::string content_type2;
+      if (!ParseContentType(pair_elem, &content_type2, &content_elem, error))
+        return false;
+
+      if (content_type2 != *content_type)
+        return BadParse("More than one content type found", error);
+    }
+  }
+
+  return true;
+}
+
+bool ParseSessionInitiate(SignalingProtocol protocol,
+                          const buzz::XmlElement* action_elem,
+                          const ContentParserMap& content_parsers,
+                          const TransportParserMap& trans_parsers,
+                          SessionInitiate* init,
+                          ParseError* error) {
+  init->owns_contents = true;
+  if (protocol == PROTOCOL_GINGLE) {
+    if (!ParseGingleContentInfos(action_elem, content_parsers,
+                                 &init->contents, error))
+      return false;
+
+    if (!ParseGingleTransportInfos(action_elem, init->contents, trans_parsers,
+                                   &init->transports, error))
+      return false;
+  } else {
+    if (!ParseJingleContentInfos(action_elem, content_parsers,
+                                 &init->contents, error))
+      return false;
+
+    if (!ParseJingleTransportInfos(action_elem, init->contents, trans_parsers,
+                                   &init->transports, error))
+      return false;
+  }
+
+  return true;
+}
+
+
+bool WriteSessionInitiate(SignalingProtocol protocol,
+                          const ContentInfos& contents,
+                          const TransportInfos& tinfos,
+                          const ContentParserMap& content_parsers,
+                          const TransportParserMap& transport_parsers,
+                          XmlElements* elems,
+                          WriteError* error) {
+  if (protocol == PROTOCOL_GINGLE) {
+    if (!WriteGingleContentInfos(contents, content_parsers, elems, error))
+      return false;
+
+    if (!WriteGingleTransportInfos(tinfos, transport_parsers,
+                                   elems, error))
+      return false;
+  } else {
+    if (!WriteJingleContentPairs(contents, content_parsers,
+                                 tinfos, transport_parsers,
+                                 elems, error))
+      return false;
+  }
+
+  return true;
+}
+
+bool ParseSessionAccept(SignalingProtocol protocol,
+                        const buzz::XmlElement* action_elem,
+                        const ContentParserMap& content_parsers,
+                        const TransportParserMap& transport_parsers,
+                        SessionAccept* accept,
+                        ParseError* error) {
+  return ParseSessionInitiate(protocol, action_elem,
+                              content_parsers, transport_parsers,
+                              accept, error);
+}
+
+bool WriteSessionAccept(SignalingProtocol protocol,
+                        const ContentInfos& contents,
+                        const TransportInfos& tinfos,
+                        const ContentParserMap& content_parsers,
+                        const TransportParserMap& transport_parsers,
+                        XmlElements* elems,
+                        WriteError* error) {
+  return WriteSessionInitiate(protocol, contents, tinfos,
+                              content_parsers, transport_parsers,
+                              elems, error);
+}
+
+bool ParseSessionTerminate(SignalingProtocol protocol,
+                           const buzz::XmlElement* action_elem,
+                           SessionTerminate* term,
+                           ParseError* error) {
+  if (protocol == PROTOCOL_GINGLE) {
+    const buzz::XmlElement* reason_elem = action_elem->FirstElement();
+    if (reason_elem != NULL) {
+      term->reason = reason_elem->Name().LocalPart();
+      const buzz::XmlElement *debug_elem = reason_elem->FirstElement();
+      if (debug_elem != NULL) {
+        term->debug_reason = debug_elem->Name().LocalPart();
+      }
+    }
+    return true;
+  } else {
+    const buzz::XmlElement* reason_elem =
+        action_elem->FirstNamed(QN_JINGLE_REASON);
+    if (reason_elem) {
+      reason_elem = reason_elem->FirstElement();
+      if (reason_elem) {
+        term->reason = reason_elem->Name().LocalPart();
+      }
+    }
+    return true;
+  }
+}
+
+void WriteSessionTerminate(SignalingProtocol protocol,
+                           const SessionTerminate& term,
+                           XmlElements* elems) {
+  if (protocol == PROTOCOL_GINGLE) {
+    elems->push_back(new buzz::XmlElement(
+        buzz::QName(true, NS_GINGLE, term.reason)));
+  } else {
+    if (!term.reason.empty()) {
+      buzz::XmlElement* reason_elem = new buzz::XmlElement(QN_JINGLE_REASON);
+      reason_elem->AddElement(new buzz::XmlElement(
+          buzz::QName(true, NS_JINGLE, term.reason)));
+      elems->push_back(reason_elem);
+    }
+  }
+}
+
+bool ParseTransportInfos(SignalingProtocol protocol,
+                         const buzz::XmlElement* action_elem,
+                         const ContentInfos& contents,
+                         const TransportParserMap& trans_parsers,
+                         TransportInfos* tinfos,
+                         ParseError* error) {
+  if (protocol == PROTOCOL_GINGLE) {
+    return ParseGingleTransportInfos(
+        action_elem, contents, trans_parsers, tinfos, error);
+  } else {
+    return ParseJingleTransportInfos(
+        action_elem, contents, trans_parsers, tinfos, error);
+  }
+}
+
+bool WriteTransportInfos(SignalingProtocol protocol,
+                         const TransportInfos& tinfos,
+                         const TransportParserMap& trans_parsers,
+                         XmlElements* elems,
+                         WriteError* error) {
+  if (protocol == PROTOCOL_GINGLE) {
+    return WriteGingleTransportInfos(tinfos, trans_parsers,
+                                     elems, error);
+  } else {
+    return WriteJingleTransportInfos(tinfos, trans_parsers,
+                                     elems, error);
+  }
+}
+
+bool ParseSessionNotify(const buzz::XmlElement* action_elem,
+                        SessionNotify* notify, ParseError* error) {
+  const buzz::XmlElement* notify_elem;
+  for (notify_elem = action_elem->FirstNamed(QN_GINGLE_NOTIFY);
+      notify_elem != NULL;
+      notify_elem = notify_elem->NextNamed(QN_GINGLE_NOTIFY)) {
+    // Note that a subsequent notify element for the same user will override a
+    // previous.  We don't merge them.
+    std::string nick(notify_elem->Attr(QN_GINGLE_NOTIFY_NICK));
+    if (nick != buzz::STR_EMPTY) {
+      MediaSources sources;
+      const buzz::XmlElement* source_elem;
+      for (source_elem = notify_elem->FirstNamed(QN_GINGLE_NOTIFY_SOURCE);
+          source_elem != NULL;
+          source_elem = source_elem->NextNamed(QN_GINGLE_NOTIFY_SOURCE)) {
+        std::string ssrc = source_elem->Attr(QN_GINGLE_NOTIFY_SOURCE_SSRC);
+        if (ssrc != buzz::STR_EMPTY) {
+          std::string mtype = source_elem->Attr(QN_GINGLE_NOTIFY_SOURCE_MTYPE);
+          if (mtype == GINGLE_NOTIFY_SOURCE_MTYPE_AUDIO) {
+            sources.audio_ssrc = strtoul(ssrc.c_str(), NULL, 10);
+          } else if (mtype == GINGLE_NOTIFY_SOURCE_MTYPE_VIDEO) {
+            sources.video_ssrc = strtoul(ssrc.c_str(), NULL, 10);
+          }
+        }
+      }
+
+      notify->nickname_to_sources.insert(
+          std::pair<std::string, MediaSources>(nick, sources));
+    }
+  }
+
+  return true;
+}
+
+bool GetUriTarget(const std::string& prefix, const std::string& str,
+                  std::string* after) {
+  size_t pos = str.find(prefix);
+  if (pos == std::string::npos)
+    return false;
+
+  *after = str.substr(pos + prefix.size(), std::string::npos);
+  return true;
+}
+
+bool ParseSessionUpdate(const buzz::XmlElement* action_elem,
+                        SessionUpdate* update, ParseError* error) {
+  // TODO: Parse the update message.
+  return true;
+}
+
+void WriteSessionView(const SessionView& view, XmlElements* elems) {
+  std::vector<VideoViewRequest>::const_iterator it;
+  for (it = view.view_requests.begin(); it != view.view_requests.end(); it++) {
+    talk_base::scoped_ptr<buzz::XmlElement> view_elem(
+        new buzz::XmlElement(QN_GINGLE_VIEW));
+    if (view_elem.get() == NULL) {
+      return;
+    }
+
+    view_elem->SetAttr(QN_GINGLE_VIEW_TYPE, GINGLE_VIEW_TYPE_STATIC);
+    view_elem->SetAttr(QN_GINGLE_VIEW_NICK, it->nick_name);
+    view_elem->SetAttr(QN_GINGLE_VIEW_MEDIA_TYPE,
+        GINGLE_VIEW_MEDIA_TYPE_VIDEO);
+
+    // A 32-bit uint, expressed as decimal, has a max of 10 digits, plus one
+    // for the null.
+    char str[11];
+    int result = talk_base::sprintfn(str, ARRAY_SIZE(str), "%u", it->ssrc);
+    if (result < 0 || result >= ARRAY_SIZE(str)) {
+      continue;
+    }
+    view_elem->SetAttr(QN_GINGLE_VIEW_SSRC, str);
+
+    // Include video-specific parameters in a child <params> element.
+    talk_base::scoped_ptr<buzz::XmlElement> params_elem(
+        new buzz::XmlElement(QN_GINGLE_VIEW_PARAMS));
+    if (params_elem.get() == NULL) {
+      return;
+    }
+
+    result = talk_base::sprintfn(str, ARRAY_SIZE(str), "%u", it->width);
+    if (result < 0 || result >= ARRAY_SIZE(str)) {
+      continue;
+    }
+    params_elem->SetAttr(QN_GINGLE_VIEW_PARAMS_WIDTH, str);
+
+    result = talk_base::sprintfn(str, ARRAY_SIZE(str), "%u", it->height);
+    if (result < 0 || result >= ARRAY_SIZE(str)) {
+      continue;
+    }
+    params_elem->SetAttr(QN_GINGLE_VIEW_PARAMS_HEIGHT, str);
+
+    result = talk_base::sprintfn(str, ARRAY_SIZE(str), "%u", it->framerate);
+    if (result < 0 || result >= ARRAY_SIZE(str)) {
+      continue;
+    }
+    params_elem->SetAttr(QN_GINGLE_VIEW_PARAMS_FRAMERATE, str);
+
+    view_elem->AddElement(params_elem.release());
+    elems->push_back(view_elem.release());
+  }
+}
+
+bool FindSessionRedirect(const buzz::XmlElement* stanza,
+                         SessionRedirect* redirect) {
+  const buzz::XmlElement* error_elem = GetXmlChild(stanza, LN_ERROR);
+  if (error_elem == NULL)
+    return false;
+
+  const buzz::XmlElement* redirect_elem =
+      error_elem->FirstNamed(QN_GINGLE_REDIRECT);
+  if (redirect_elem == NULL)
+    redirect_elem = error_elem->FirstNamed(buzz::QN_STANZA_REDIRECT);
+  if (redirect_elem == NULL)
+    return false;
+
+  if (!GetUriTarget(STR_REDIRECT_PREFIX, redirect_elem->BodyText(),
+                    &redirect->target))
+    return false;
+
+  return true;
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/sessionmessages.h b/talk/p2p/base/sessionmessages.h
new file mode 100644
index 0000000..affb4d9
--- /dev/null
+++ b/talk/p2p/base/sessionmessages.h
@@ -0,0 +1,272 @@
+/*
+ * 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.
+ */
+
+#ifndef TALK_P2P_BASE_SESSIONMESSAGES_H_
+#define TALK_P2P_BASE_SESSIONMESSAGES_H_
+
+#include <string>
+#include <vector>
+#include <map>
+
+#include "talk/xmllite/xmlelement.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/sessiondescription.h"  // Needed to delete contents.
+#include "talk/base/basictypes.h"
+
+namespace cricket {
+
+struct ParseError;
+struct WriteError;
+class Candidate;
+class ContentParser;
+class TransportParser;
+
+typedef std::vector<buzz::XmlElement*> XmlElements;
+typedef std::vector<Candidate> Candidates;
+typedef std::map<std::string, ContentParser*> ContentParserMap;
+typedef std::map<std::string, TransportParser*> TransportParserMap;
+
+enum ActionType {
+  ACTION_UNKNOWN,
+
+  ACTION_SESSION_INITIATE,
+  ACTION_SESSION_INFO,
+  ACTION_SESSION_ACCEPT,
+  ACTION_SESSION_REJECT,
+  ACTION_SESSION_TERMINATE,
+
+  ACTION_TRANSPORT_INFO,
+  ACTION_TRANSPORT_ACCEPT,
+
+  // TODO: Make better names for these when we think of a
+  // "jingley" way of signaling them.  Even better, remove them from
+  // being needed at all.
+  ACTION_NOTIFY,
+  ACTION_UPDATE,
+  ACTION_VIEW,
+};
+
+// Abstraction of a <jingle> element within an <iq> stanza, per XMPP
+// standard XEP-166.  Can be serialized into multiple protocols,
+// including the standard (Jingle) and the draft standard (Gingle).
+// In general, used to communicate actions related to a p2p session,
+// such accept, initiate, terminate, etc.
+
+struct SessionMessage {
+  SessionMessage() : action_elem(NULL), stanza(NULL) {}
+
+  SessionMessage(SignalingProtocol protocol, ActionType type,
+                 const std::string& sid, const std::string& initiator) :
+      protocol(protocol), type(type), sid(sid), initiator(initiator),
+      action_elem(NULL), stanza(NULL) {}
+
+  std::string id;
+  std::string from;
+  std::string to;
+  SignalingProtocol protocol;
+  ActionType type;
+  std::string sid;  // session id
+  std::string initiator;
+
+  // Used for further parsing when necessary.
+  // Represents <session> or <jingle>.
+  const buzz::XmlElement* action_elem;
+  // Mostly used for debugging.
+  const buzz::XmlElement* stanza;
+};
+
+// A TransportInfo is NOT a transport-info message.  It is comparable
+// to a "ContentInfo". A transport-info message is basically just a
+// collection of TransportInfos.
+struct TransportInfo {
+  TransportInfo() {}
+
+  TransportInfo(const std::string& content_name,
+                const std::string& transport_type,
+                const Candidates& candidates)
+      : content_name(content_name),
+        transport_type(transport_type),
+        candidates(candidates) {}
+
+  std::string content_name;
+  std::string transport_type;  // xmlns of <transport>
+  Candidates candidates;
+};
+
+typedef std::vector<TransportInfo> TransportInfos;
+
+struct SessionInitiate {
+  SessionInitiate() : owns_contents(false) {}
+
+  ~SessionInitiate() {
+    if (owns_contents) {
+      for (ContentInfos::iterator content = contents.begin();
+           content != contents.end(); content++) {
+        delete content->description;
+      }
+    }
+  }
+
+  // Caller takes ownership of contents.
+  ContentInfos ClearContents() {
+    ContentInfos out;
+    contents.swap(out);
+    owns_contents = false;
+    return out;
+  }
+
+  bool owns_contents;
+  ContentInfos contents;
+  TransportInfos transports;
+};
+
+// Right now, a SessionAccept is functionally equivalent to a SessionInitiate.
+typedef SessionInitiate SessionAccept;
+
+struct SessionTerminate {
+  SessionTerminate() {}
+
+  explicit SessionTerminate(const std::string& reason) :
+      reason(reason) {}
+
+  std::string reason;
+  std::string debug_reason;
+};
+
+struct SessionRedirect {
+  std::string target;
+};
+
+// Holds the ssrcs for a user's media streams.
+struct MediaSources {
+  uint32 audio_ssrc;
+  uint32 video_ssrc;
+  MediaSources() : audio_ssrc(0), video_ssrc(0) {}
+};
+
+typedef std::map<std::string, MediaSources> StringToMediaSourcesMap;
+
+struct SessionNotify {
+  // A mapping of room users (identified by their nicknames) to their ssrcs.
+  StringToMediaSourcesMap nickname_to_sources;
+};
+
+// TODO: Populate the update message.
+struct SessionUpdate {
+};
+
+// Represents an individual <view> element in the <session type="view">
+// message.
+struct VideoViewRequest {
+  std::string nick_name;
+  uint32 ssrc;
+  uint32 width;
+  uint32 height;
+  uint32 framerate;
+
+  VideoViewRequest(const std::string& nick_name, uint32 ssrc, uint32 width,
+            uint32 height, uint32 framerate) :
+      nick_name(nick_name), ssrc(ssrc), width(width), height(height),
+      framerate(framerate) {}
+};
+
+typedef std::vector<VideoViewRequest> VideoViewRequestVector;
+
+struct SessionView {
+  VideoViewRequestVector view_requests;
+};
+
+bool IsSessionMessage(const buzz::XmlElement* stanza);
+bool ParseSessionMessage(const buzz::XmlElement* stanza,
+                         SessionMessage* msg,
+                         ParseError* error);
+// Will return an error if there is more than one content type.
+bool ParseContentType(SignalingProtocol protocol,
+                      const buzz::XmlElement* action_elem,
+                      std::string* content_type,
+                      ParseError* error);
+void WriteSessionMessage(const SessionMessage& msg,
+                         const XmlElements& action_elems,
+                         buzz::XmlElement* stanza);
+bool ParseSessionInitiate(SignalingProtocol protocol,
+                          const buzz::XmlElement* action_elem,
+                          const ContentParserMap& content_parsers,
+                          const TransportParserMap& transport_parsers,
+                          SessionInitiate* init,
+                          ParseError* error);
+bool WriteSessionInitiate(SignalingProtocol protocol,
+                          const ContentInfos& contents,
+                          const TransportInfos& tinfos,
+                          const ContentParserMap& content_parsers,
+                          const TransportParserMap& transport_parsers,
+                          XmlElements* elems,
+                          WriteError* error);
+bool ParseSessionAccept(SignalingProtocol protocol,
+                        const buzz::XmlElement* action_elem,
+                        const ContentParserMap& content_parsers,
+                        const TransportParserMap& transport_parsers,
+                        SessionAccept* accept,
+                        ParseError* error);
+bool WriteSessionAccept(SignalingProtocol protocol,
+                        const ContentInfos& contents,
+                        const TransportInfos& tinfos,
+                        const ContentParserMap& content_parsers,
+                        const TransportParserMap& transport_parsers,
+                        XmlElements* elems,
+                        WriteError* error);
+bool ParseSessionTerminate(SignalingProtocol protocol,
+                           const buzz::XmlElement* action_elem,
+                           SessionTerminate* term,
+                           ParseError* error);
+void WriteSessionTerminate(SignalingProtocol protocol,
+                           const SessionTerminate& term,
+                           XmlElements* elems);
+// Since a TransportInfo is not a transport-info message, and a
+// transport-info message is just a collection of TransportInfos, we
+// say Parse/Write TransportInfos for transport-info messages.
+bool ParseTransportInfos(SignalingProtocol protocol,
+                         const buzz::XmlElement* action_elem,
+                         const ContentInfos& contents,
+                         const TransportParserMap& trans_parsers,
+                         TransportInfos* tinfos,
+                         ParseError* error);
+bool WriteTransportInfos(SignalingProtocol protocol,
+                         const TransportInfos& tinfos,
+                         const TransportParserMap& trans_parsers,
+                         XmlElements* elems,
+                         WriteError* error);
+bool ParseSessionNotify(const buzz::XmlElement* action_elem,
+                        SessionNotify* notify, ParseError* error);
+bool ParseSessionUpdate(const buzz::XmlElement* action_elem,
+                        SessionUpdate* update, ParseError* error);
+void WriteSessionView(const SessionView& view, XmlElements* elems);
+// Handles both Gingle and Jingle syntax.
+bool FindSessionRedirect(const buzz::XmlElement* stanza,
+                         SessionRedirect* redirect);
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_SESSIONMESSAGES_H_
diff --git a/talk/p2p/base/stun.cc b/talk/p2p/base/stun.cc
new file mode 100644
index 0000000..ad8b5ce
--- /dev/null
+++ b/talk/p2p/base/stun.cc
@@ -0,0 +1,580 @@
+/*
+ * 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/p2p/base/stun.h"
+
+#include <cstring>
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+
+using talk_base::ByteBuffer;
+
+namespace cricket {
+
+const std::string STUN_ERROR_REASON_BAD_REQUEST = "BAD REQUEST";
+const std::string STUN_ERROR_REASON_UNAUTHORIZED = "UNAUTHORIZED";
+const std::string STUN_ERROR_REASON_UNKNOWN_ATTRIBUTE = "UNKNOWN ATTRIBUTE";
+const std::string STUN_ERROR_REASON_STALE_CREDENTIALS = "STALE CREDENTIALS";
+const std::string STUN_ERROR_REASON_INTEGRITY_CHECK_FAILURE = "INTEGRITY CHECK FAILURE";
+const std::string STUN_ERROR_REASON_MISSING_USERNAME = "MISSING USERNAME";
+const std::string STUN_ERROR_REASON_USE_TLS = "USE TLS";
+const std::string STUN_ERROR_REASON_SERVER_ERROR = "SERVER ERROR";
+const std::string STUN_ERROR_REASON_GLOBAL_FAILURE = "GLOBAL FAILURE";
+
+StunMessage::StunMessage() : type_(0), length_(0),
+    transaction_id_("0000000000000000") {
+  ASSERT(transaction_id_.size() == 16);
+  attrs_ = new std::vector<StunAttribute*>();
+}
+
+StunMessage::~StunMessage() {
+  for (unsigned i = 0; i < attrs_->size(); i++)
+    delete (*attrs_)[i];
+  delete attrs_;
+}
+
+void StunMessage::SetTransactionID(const std::string& str) {
+  ASSERT(str.size() == 16);
+  transaction_id_ = str;
+}
+
+void StunMessage::AddAttribute(StunAttribute* attr) {
+  attrs_->push_back(attr);
+  length_ += attr->length() + 4;
+}
+
+const StunAddressAttribute*
+StunMessage::GetAddress(StunAttributeType type) const {
+  switch (type) {
+  case STUN_ATTR_MAPPED_ADDRESS:
+  case STUN_ATTR_RESPONSE_ADDRESS:
+  case STUN_ATTR_SOURCE_ADDRESS:
+  case STUN_ATTR_CHANGED_ADDRESS:
+  case STUN_ATTR_REFLECTED_FROM:
+  case STUN_ATTR_ALTERNATE_SERVER:
+  case STUN_ATTR_DESTINATION_ADDRESS:
+  case STUN_ATTR_SOURCE_ADDRESS2:
+    return reinterpret_cast<const StunAddressAttribute*>(GetAttribute(type));
+
+  default:
+    ASSERT(0);
+    return 0;
+  }
+}
+
+const StunUInt32Attribute*
+StunMessage::GetUInt32(StunAttributeType type) const {
+  switch (type) {
+  case STUN_ATTR_CHANGE_REQUEST:
+  case STUN_ATTR_LIFETIME:
+  case STUN_ATTR_BANDWIDTH:
+  case STUN_ATTR_OPTIONS:
+    return reinterpret_cast<const StunUInt32Attribute*>(GetAttribute(type));
+
+  default:
+    ASSERT(0);
+    return 0;
+  }
+}
+
+const StunByteStringAttribute*
+StunMessage::GetByteString(StunAttributeType type) const {
+  switch (type) {
+  case STUN_ATTR_USERNAME:
+  case STUN_ATTR_PASSWORD:
+  case STUN_ATTR_MESSAGE_INTEGRITY:
+  case STUN_ATTR_DATA:
+  case STUN_ATTR_MAGIC_COOKIE:
+    return reinterpret_cast<const StunByteStringAttribute*>(GetAttribute(type));
+
+  default:
+    ASSERT(0);
+    return 0;
+  }
+}
+
+const StunErrorCodeAttribute* StunMessage::GetErrorCode() const {
+  return reinterpret_cast<const StunErrorCodeAttribute*>(
+      GetAttribute(STUN_ATTR_ERROR_CODE));
+}
+
+const StunUInt16ListAttribute* StunMessage::GetUnknownAttributes() const {
+  return reinterpret_cast<const StunUInt16ListAttribute*>(
+      GetAttribute(STUN_ATTR_UNKNOWN_ATTRIBUTES));
+}
+
+const StunTransportPrefsAttribute* StunMessage::GetTransportPrefs() const {
+  return reinterpret_cast<const StunTransportPrefsAttribute*>(
+      GetAttribute(STUN_ATTR_TRANSPORT_PREFERENCES));
+}
+
+const StunAttribute* StunMessage::GetAttribute(StunAttributeType type) const {
+  for (unsigned i = 0; i < attrs_->size(); i++) {
+    if ((*attrs_)[i]->type() == type)
+      return (*attrs_)[i];
+  }
+  return 0;
+}
+
+bool StunMessage::Read(ByteBuffer* buf) {
+  if (!buf->ReadUInt16(&type_))
+    return false;
+
+  if (type_ & 0x8000) {
+    // rtp and rtcp set MSB of first byte, since first two bits are version,
+    // and version is always 2 (10).  If set, this is not a stun packet.
+    return false;
+  }
+
+  if (!buf->ReadUInt16(&length_))
+    return false;
+
+  std::string transaction_id;
+  if (!buf->ReadString(&transaction_id, 16))
+    return false;
+  ASSERT(transaction_id.size() == 16);
+  transaction_id_ = transaction_id;
+
+  if (length_ > buf->Length())
+    return false;
+
+  attrs_->resize(0);
+
+  size_t rest = buf->Length() - length_;
+  while (buf->Length() > rest) {
+    uint16 attr_type, attr_length;
+    if (!buf->ReadUInt16(&attr_type))
+      return false;
+    if (!buf->ReadUInt16(&attr_length))
+      return false;
+
+    StunAttribute* attr = StunAttribute::Create(attr_type, attr_length);
+    if (!attr || !attr->Read(buf))
+      return false;
+
+    attrs_->push_back(attr);
+  }
+
+  if (buf->Length() != rest) {
+    // fixme: shouldn't be doing this
+    LOG(LERROR) << "wrong message length (" << rest << " != " << buf->Length()
+                << ")";
+    return false;
+  }
+
+  return true;
+}
+
+void StunMessage::Write(ByteBuffer* buf) const {
+  buf->WriteUInt16(type_);
+  buf->WriteUInt16(length_);
+  buf->WriteString(transaction_id_);
+
+  for (unsigned i = 0; i < attrs_->size(); i++) {
+    buf->WriteUInt16((*attrs_)[i]->type());
+    buf->WriteUInt16((*attrs_)[i]->length());
+    (*attrs_)[i]->Write(buf);
+  }
+}
+
+StunAttribute::StunAttribute(uint16 type, uint16 length)
+    : type_(type), length_(length) {
+}
+
+StunAttribute* StunAttribute::Create(uint16 type, uint16 length) {
+  switch (type) {
+  case STUN_ATTR_MAPPED_ADDRESS:
+  case STUN_ATTR_RESPONSE_ADDRESS:
+  case STUN_ATTR_SOURCE_ADDRESS:
+  case STUN_ATTR_CHANGED_ADDRESS:
+  case STUN_ATTR_REFLECTED_FROM:
+  case STUN_ATTR_ALTERNATE_SERVER:
+  case STUN_ATTR_DESTINATION_ADDRESS:
+  case STUN_ATTR_SOURCE_ADDRESS2:
+    if (length != StunAddressAttribute::SIZE)
+      return 0;
+    return new StunAddressAttribute(type);
+
+  case STUN_ATTR_CHANGE_REQUEST:
+  case STUN_ATTR_LIFETIME:
+  case STUN_ATTR_BANDWIDTH:
+  case STUN_ATTR_OPTIONS:
+    if (length != StunUInt32Attribute::SIZE)
+      return 0;
+    return new StunUInt32Attribute(type);
+
+  case STUN_ATTR_USERNAME:
+  case STUN_ATTR_PASSWORD:
+  case STUN_ATTR_MAGIC_COOKIE:
+    return (length % 4 == 0) ? new StunByteStringAttribute(type, length) : 0;
+
+  case STUN_ATTR_MESSAGE_INTEGRITY:
+    return (length == 20) ? new StunByteStringAttribute(type, length) : 0;
+
+  case STUN_ATTR_DATA:
+    return new StunByteStringAttribute(type, length);
+
+  case STUN_ATTR_ERROR_CODE:
+    if (length < StunErrorCodeAttribute::MIN_SIZE)
+      return 0;
+    return new StunErrorCodeAttribute(type, length);
+
+  case STUN_ATTR_UNKNOWN_ATTRIBUTES:
+    return (length % 2 == 0) ? new StunUInt16ListAttribute(type, length) : 0;
+
+  case STUN_ATTR_TRANSPORT_PREFERENCES:
+    if ((length != StunTransportPrefsAttribute::SIZE1) &&
+        (length != StunTransportPrefsAttribute::SIZE2))
+      return 0;
+    return new StunTransportPrefsAttribute(type, length);
+
+  default:
+    return 0;
+  }
+}
+
+StunAddressAttribute* StunAttribute::CreateAddress(uint16 type) {
+  switch (type) {
+  case STUN_ATTR_MAPPED_ADDRESS:
+  case STUN_ATTR_RESPONSE_ADDRESS:
+  case STUN_ATTR_SOURCE_ADDRESS:
+  case STUN_ATTR_CHANGED_ADDRESS:
+  case STUN_ATTR_REFLECTED_FROM:
+  case STUN_ATTR_ALTERNATE_SERVER:
+  case STUN_ATTR_DESTINATION_ADDRESS:
+  case STUN_ATTR_SOURCE_ADDRESS2:
+    return new StunAddressAttribute(type);
+
+  default:
+    ASSERT(false);
+    return 0;
+  }
+}
+
+StunUInt32Attribute* StunAttribute::CreateUInt32(uint16 type) {
+  switch (type) {
+  case STUN_ATTR_CHANGE_REQUEST:
+  case STUN_ATTR_LIFETIME:
+  case STUN_ATTR_BANDWIDTH:
+  case STUN_ATTR_OPTIONS:
+    return new StunUInt32Attribute(type);
+
+  default:
+    ASSERT(false);
+    return 0;
+  }
+}
+
+StunByteStringAttribute* StunAttribute::CreateByteString(uint16 type) {
+  switch (type) {
+  case STUN_ATTR_USERNAME:
+  case STUN_ATTR_PASSWORD:
+  case STUN_ATTR_MESSAGE_INTEGRITY:
+  case STUN_ATTR_DATA:
+  case STUN_ATTR_MAGIC_COOKIE:
+    return new StunByteStringAttribute(type, 0);
+
+  default:
+    ASSERT(false);
+    return 0;
+  }
+}
+
+StunErrorCodeAttribute* StunAttribute::CreateErrorCode() {
+  return new StunErrorCodeAttribute(
+      STUN_ATTR_ERROR_CODE, StunErrorCodeAttribute::MIN_SIZE);
+}
+
+StunUInt16ListAttribute* StunAttribute::CreateUnknownAttributes() {
+  return new StunUInt16ListAttribute(STUN_ATTR_UNKNOWN_ATTRIBUTES, 0);
+}
+
+StunTransportPrefsAttribute* StunAttribute::CreateTransportPrefs() {
+  return new StunTransportPrefsAttribute(
+      STUN_ATTR_TRANSPORT_PREFERENCES, StunTransportPrefsAttribute::SIZE1);
+}
+
+StunAddressAttribute::StunAddressAttribute(uint16 type)
+    : StunAttribute(type, SIZE), family_(0), port_(0), ip_(0) {
+}
+
+bool StunAddressAttribute::Read(ByteBuffer* buf) {
+  uint8 dummy;
+  if (!buf->ReadUInt8(&dummy))
+    return false;
+  if (!buf->ReadUInt8(&family_))
+    return false;
+  if (!buf->ReadUInt16(&port_))
+    return false;
+  if (!buf->ReadUInt32(&ip_))
+    return false;
+  return true;
+}
+
+void StunAddressAttribute::Write(ByteBuffer* buf) const {
+  buf->WriteUInt8(0);
+  buf->WriteUInt8(family_);
+  buf->WriteUInt16(port_);
+  buf->WriteUInt32(ip_);
+}
+
+StunUInt32Attribute::StunUInt32Attribute(uint16 type)
+    : StunAttribute(type, SIZE), bits_(0) {
+}
+
+bool StunUInt32Attribute::GetBit(int index) const {
+  ASSERT((0 <= index) && (index < 32));
+  return static_cast<bool>((bits_ >> index) & 0x1);
+}
+
+void StunUInt32Attribute::SetBit(int index, bool value) {
+  ASSERT((0 <= index) && (index < 32));
+  bits_ &= ~(1 << index);
+  bits_ |= value ? (1 << index) : 0;
+}
+
+bool StunUInt32Attribute::Read(ByteBuffer* buf) {
+  if (!buf->ReadUInt32(&bits_))
+    return false;
+  return true;
+}
+
+void StunUInt32Attribute::Write(ByteBuffer* buf) const {
+  buf->WriteUInt32(bits_);
+}
+
+StunByteStringAttribute::StunByteStringAttribute(uint16 type, uint16 length)
+    : StunAttribute(type, length), bytes_(0) {
+}
+
+StunByteStringAttribute::~StunByteStringAttribute() {
+  delete [] bytes_;
+}
+
+void StunByteStringAttribute::SetBytes(char* bytes, uint16 length) {
+  delete [] bytes_;
+  bytes_ = bytes;
+  SetLength(length);
+}
+
+void StunByteStringAttribute::CopyBytes(const char* bytes) {
+  CopyBytes(bytes, static_cast<uint16>(strlen(bytes)));
+}
+
+void StunByteStringAttribute::CopyBytes(const void* bytes, uint16 length) {
+  char* new_bytes = new char[length];
+  std::memcpy(new_bytes, bytes, length);
+  SetBytes(new_bytes, length);
+}
+
+uint8 StunByteStringAttribute::GetByte(int index) const {
+  ASSERT(bytes_ != NULL);
+  ASSERT((0 <= index) && (index < length()));
+  return static_cast<uint8>(bytes_[index]);
+}
+
+void StunByteStringAttribute::SetByte(int index, uint8 value) {
+  ASSERT(bytes_ != NULL);
+  ASSERT((0 <= index) && (index < length()));
+  bytes_[index] = value;
+}
+
+bool StunByteStringAttribute::Read(ByteBuffer* buf) {
+  bytes_ = new char[length()];
+  if (!buf->ReadBytes(bytes_, length()))
+    return false;
+  return true;
+}
+
+void StunByteStringAttribute::Write(ByteBuffer* buf) const {
+  buf->WriteBytes(bytes_, length());
+}
+
+StunErrorCodeAttribute::StunErrorCodeAttribute(uint16 type, uint16 length)
+    : StunAttribute(type, length), class_(0), number_(0) {
+}
+
+StunErrorCodeAttribute::~StunErrorCodeAttribute() {
+}
+
+void StunErrorCodeAttribute::SetErrorCode(uint32 code) {
+  class_ = (uint8)((code >> 8) & 0x7);
+  number_ = (uint8)(code & 0xff);
+}
+
+void StunErrorCodeAttribute::SetReason(const std::string& reason) {
+  SetLength(MIN_SIZE + static_cast<uint16>(reason.size()));
+  reason_ = reason;
+}
+
+bool StunErrorCodeAttribute::Read(ByteBuffer* buf) {
+  uint32 val;
+  if (!buf->ReadUInt32(&val))
+    return false;
+
+  if ((val >> 11) != 0)
+    LOG(LERROR) << "error-code bits not zero";
+
+  SetErrorCode(val);
+
+  if (!buf->ReadString(&reason_, length() - 4))
+    return false;
+
+  return true;
+}
+
+void StunErrorCodeAttribute::Write(ByteBuffer* buf) const {
+  buf->WriteUInt32(error_code());
+  buf->WriteString(reason_);
+}
+
+StunUInt16ListAttribute::StunUInt16ListAttribute(uint16 type, uint16 length)
+    : StunAttribute(type, length) {
+  attr_types_ = new std::vector<uint16>();
+}
+
+StunUInt16ListAttribute::~StunUInt16ListAttribute() {
+  delete attr_types_;
+}
+
+size_t StunUInt16ListAttribute::Size() const {
+  return attr_types_->size();
+}
+
+uint16 StunUInt16ListAttribute::GetType(int index) const {
+  return (*attr_types_)[index];
+}
+
+void StunUInt16ListAttribute::SetType(int index, uint16 value) {
+  (*attr_types_)[index] = value;
+}
+
+void StunUInt16ListAttribute::AddType(uint16 value) {
+  attr_types_->push_back(value);
+  SetLength(static_cast<uint16>(attr_types_->size() * 2));
+}
+
+bool StunUInt16ListAttribute::Read(ByteBuffer* buf) {
+  for (int i = 0; i < length() / 2; i++) {
+    uint16 attr;
+    if (!buf->ReadUInt16(&attr))
+      return false;
+    attr_types_->push_back(attr);
+  }
+  return true;
+}
+
+void StunUInt16ListAttribute::Write(ByteBuffer* buf) const {
+  for (unsigned i = 0; i < attr_types_->size(); i++)
+    buf->WriteUInt16((*attr_types_)[i]);
+}
+
+StunTransportPrefsAttribute::StunTransportPrefsAttribute(
+    uint16 type, uint16 length)
+    : StunAttribute(type, length), preallocate_(false), prefs_(0), addr_(0) {
+}
+
+StunTransportPrefsAttribute::~StunTransportPrefsAttribute() {
+  delete addr_;
+}
+
+void StunTransportPrefsAttribute::SetPreallocateAddress(
+    StunAddressAttribute* addr) {
+  if (!addr) {
+    preallocate_ = false;
+    addr_ = 0;
+    SetLength(SIZE1);
+  } else {
+    preallocate_ = true;
+    addr_ = addr;
+    SetLength(SIZE2);
+  }
+}
+
+bool StunTransportPrefsAttribute::Read(ByteBuffer* buf) {
+  uint32 val;
+  if (!buf->ReadUInt32(&val))
+    return false;
+
+  if ((val >> 3) != 0)
+    LOG(LERROR) << "transport-preferences bits not zero";
+
+  preallocate_ = static_cast<bool>((val >> 2) & 0x1);
+  prefs_ = (uint8)(val & 0x3);
+
+  if (preallocate_ && (prefs_ == 3))
+    LOG(LERROR) << "transport-preferences imcompatible P and Typ";
+
+  if (!preallocate_) {
+    if (length() != StunUInt32Attribute::SIZE)
+      return false;
+  } else {
+    if (length() != StunUInt32Attribute::SIZE + StunAddressAttribute::SIZE)
+      return false;
+
+    addr_ = new StunAddressAttribute(STUN_ATTR_SOURCE_ADDRESS);
+    addr_->Read(buf);
+  }
+
+  return true;
+}
+
+void StunTransportPrefsAttribute::Write(ByteBuffer* buf) const {
+  buf->WriteUInt32((preallocate_ ? 4 : 0) | prefs_);
+
+  if (preallocate_)
+    addr_->Write(buf);
+}
+
+StunMessageType GetStunResponseType(StunMessageType request_type) {
+  switch (request_type) {
+  case STUN_SHARED_SECRET_REQUEST:
+    return STUN_SHARED_SECRET_RESPONSE;
+  case STUN_ALLOCATE_REQUEST:
+    return STUN_ALLOCATE_RESPONSE;
+  case STUN_SEND_REQUEST:
+    return STUN_SEND_RESPONSE;
+  default:
+    return STUN_BINDING_RESPONSE;
+  }
+}
+
+StunMessageType GetStunErrorResponseType(StunMessageType request_type) {
+  switch (request_type) {
+  case STUN_SHARED_SECRET_REQUEST:
+    return STUN_SHARED_SECRET_ERROR_RESPONSE;
+  case STUN_ALLOCATE_REQUEST:
+    return STUN_ALLOCATE_ERROR_RESPONSE;
+  case STUN_SEND_REQUEST:
+    return STUN_SEND_ERROR_RESPONSE;
+  default:
+    return STUN_BINDING_ERROR_RESPONSE;
+  }
+}
+
+} // namespace cricket
diff --git a/talk/p2p/base/stun.h b/talk/p2p/base/stun.h
new file mode 100644
index 0000000..2282fed
--- /dev/null
+++ b/talk/p2p/base/stun.h
@@ -0,0 +1,365 @@
+/*
+ * 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 __STUN_H__
+#define __STUN_H__
+
+// This file contains classes for dealing with the STUN and TURN protocols.
+// Both protocols use the same wire format.
+
+#include <string>
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/bytebuffer.h"
+
+namespace cricket {
+
+// These are the types of STUN & TURN messages as of last check.
+enum StunMessageType {
+  STUN_BINDING_REQUEST              = 0x0001,
+  STUN_BINDING_RESPONSE             = 0x0101,
+  STUN_BINDING_ERROR_RESPONSE       = 0x0111,
+  STUN_SHARED_SECRET_REQUEST        = 0x0002,
+  STUN_SHARED_SECRET_RESPONSE       = 0x0102,
+  STUN_SHARED_SECRET_ERROR_RESPONSE = 0x0112,
+  STUN_ALLOCATE_REQUEST             = 0x0003,
+  STUN_ALLOCATE_RESPONSE            = 0x0103,
+  STUN_ALLOCATE_ERROR_RESPONSE      = 0x0113,
+  STUN_SEND_REQUEST                 = 0x0004,
+  STUN_SEND_RESPONSE                = 0x0104,
+  STUN_SEND_ERROR_RESPONSE          = 0x0114,
+  STUN_DATA_INDICATION              = 0x0115
+};
+
+// These are the types of attributes defined in STUN & TURN.  Next to each is
+// the name of the class (T is StunTAttribute) that implements that type.
+enum StunAttributeType {
+  STUN_ATTR_MAPPED_ADDRESS        = 0x0001, // Address
+  STUN_ATTR_RESPONSE_ADDRESS      = 0x0002, // Address
+  STUN_ATTR_CHANGE_REQUEST        = 0x0003, // UInt32
+  STUN_ATTR_SOURCE_ADDRESS        = 0x0004, // Address
+  STUN_ATTR_CHANGED_ADDRESS       = 0x0005, // Address
+  STUN_ATTR_USERNAME              = 0x0006, // ByteString, multiple of 4 bytes
+  STUN_ATTR_PASSWORD              = 0x0007, // ByteString, multiple of 4 bytes
+  STUN_ATTR_MESSAGE_INTEGRITY     = 0x0008, // ByteString, 20 bytes
+  STUN_ATTR_ERROR_CODE            = 0x0009, // ErrorCode
+  STUN_ATTR_UNKNOWN_ATTRIBUTES    = 0x000a, // UInt16List
+  STUN_ATTR_REFLECTED_FROM        = 0x000b, // Address
+  STUN_ATTR_TRANSPORT_PREFERENCES = 0x000c, // TransportPrefs
+  STUN_ATTR_LIFETIME              = 0x000d, // UInt32
+  STUN_ATTR_ALTERNATE_SERVER      = 0x000e, // Address
+  STUN_ATTR_MAGIC_COOKIE          = 0x000f, // ByteString, 4 bytes
+  STUN_ATTR_BANDWIDTH             = 0x0010, // UInt32
+  STUN_ATTR_DESTINATION_ADDRESS   = 0x0011, // Address
+  STUN_ATTR_SOURCE_ADDRESS2       = 0x0012, // Address
+  STUN_ATTR_DATA                  = 0x0013, // ByteString
+  STUN_ATTR_OPTIONS               = 0x8001  // UInt32
+};
+
+enum StunErrorCodes {
+  STUN_ERROR_BAD_REQUEST          = 400,
+  STUN_ERROR_UNAUTHORIZED         = 401,
+  STUN_ERROR_UNKNOWN_ATTRIBUTE    = 420,
+  STUN_ERROR_STALE_CREDENTIALS    = 430,
+  STUN_ERROR_INTEGRITY_CHECK_FAILURE = 431,
+  STUN_ERROR_MISSING_USERNAME     = 432,
+  STUN_ERROR_USE_TLS              = 433,
+  STUN_ERROR_SERVER_ERROR         = 500,
+  STUN_ERROR_GLOBAL_FAILURE       = 600
+};
+
+extern const std::string STUN_ERROR_REASON_BAD_REQUEST;
+extern const std::string STUN_ERROR_REASON_UNAUTHORIZED;
+extern const std::string STUN_ERROR_REASON_UNKNOWN_ATTRIBUTE;
+extern const std::string STUN_ERROR_REASON_STALE_CREDENTIALS;
+extern const std::string STUN_ERROR_REASON_INTEGRITY_CHECK_FAILURE;
+extern const std::string STUN_ERROR_REASON_MISSING_USERNAME;
+extern const std::string STUN_ERROR_REASON_USE_TLS;
+extern const std::string STUN_ERROR_REASON_SERVER_ERROR;
+extern const std::string STUN_ERROR_REASON_GLOBAL_FAILURE;
+
+class StunAttribute;
+class StunAddressAttribute;
+class StunUInt32Attribute;
+class StunByteStringAttribute;
+class StunErrorCodeAttribute;
+class StunUInt16ListAttribute;
+class StunTransportPrefsAttribute;
+
+// Records a complete STUN/TURN message.  Each message consists of a type and
+// any number of attributes.  Each attribute is parsed into an instance of an
+// appropriate class (see above).  The Get* methods will return instances of
+// that attribute class.
+class StunMessage {
+public:
+  StunMessage();
+  ~StunMessage();
+
+  StunMessageType type() const { return static_cast<StunMessageType>(type_); }
+  uint16 length() const { return length_; }
+  const std::string& transaction_id() const { return transaction_id_; }
+
+  void SetType(StunMessageType type) { type_ = type; }
+  void SetTransactionID(const std::string& str);
+
+  const StunAddressAttribute* GetAddress(StunAttributeType type) const;
+  const StunUInt32Attribute* GetUInt32(StunAttributeType type) const;
+  const StunByteStringAttribute* GetByteString(StunAttributeType type) const;
+  const StunErrorCodeAttribute* GetErrorCode() const;
+  const StunUInt16ListAttribute* GetUnknownAttributes() const;
+  const StunTransportPrefsAttribute* GetTransportPrefs() const;
+
+  void AddAttribute(StunAttribute* attr);
+
+  // Parses the STUN/TURN packet in the given buffer and records it here.  The
+  // return value indicates whether this was successful.
+  bool Read(talk_base::ByteBuffer* buf);
+
+  // Writes this object into a STUN/TURN packet.  Return value is true if
+  // successful.
+  void Write(talk_base::ByteBuffer* buf) const;
+
+private:
+  uint16 type_;
+  uint16 length_;
+  std::string transaction_id_;
+  std::vector<StunAttribute*>* attrs_;
+
+  const StunAttribute* GetAttribute(StunAttributeType type) const;
+};
+
+// Base class for all STUN/TURN attributes.
+class StunAttribute {
+public:
+  virtual ~StunAttribute() {}
+
+  StunAttributeType type() const {
+    return static_cast<StunAttributeType>(type_);
+  } 
+  uint16 length() const { return length_; }
+
+  // Reads the body (not the type or length) for this type of attribute from
+  // the given buffer.  Return value is true if successful.
+  virtual bool Read(talk_base::ByteBuffer* buf) = 0;
+
+  // Writes the body (not the type or length) to the given buffer.  Return
+  // value is true if successful.
+  virtual void Write(talk_base::ByteBuffer* buf) const = 0;
+
+  // Creates an attribute object with the given type and len.
+  static StunAttribute* Create(uint16 type, uint16 length);
+
+  // Creates an attribute object with the given type and smallest length.
+  static StunAddressAttribute* CreateAddress(uint16 type);
+  static StunUInt32Attribute* CreateUInt32(uint16 type);
+  static StunByteStringAttribute* CreateByteString(uint16 type);
+  static StunErrorCodeAttribute* CreateErrorCode();
+  static StunUInt16ListAttribute* CreateUnknownAttributes();
+  static StunTransportPrefsAttribute* CreateTransportPrefs();
+
+protected:
+  StunAttribute(uint16 type, uint16 length);
+
+  void SetLength(uint16 length) { length_ = length; }
+
+private:
+  uint16 type_;
+  uint16 length_;
+};
+
+// Implements STUN/TURN attributes that record an Internet address.
+class StunAddressAttribute : public StunAttribute {
+public:
+  StunAddressAttribute(uint16 type);
+
+#if (_MSC_VER < 1300)
+  enum { SIZE = 8 };
+#else
+  static const uint16 SIZE = 8;
+#endif
+
+  uint8 family() const { return family_; }
+  uint16 port() const { return port_; }
+  uint32 ip() const { return ip_; }
+
+  void SetFamily(uint8 family) { family_ = family; }
+  void SetIP(uint32 ip) { ip_ = ip; }
+  void SetPort(uint16 port) { port_ = port; }
+
+  bool Read(talk_base::ByteBuffer* buf);
+  void Write(talk_base::ByteBuffer* buf) const;
+
+private:
+  uint8 family_;
+  uint16 port_;
+  uint32 ip_;
+};
+
+// Implements STUN/TURN attributs that record a 32-bit integer.
+class StunUInt32Attribute : public StunAttribute {
+public:
+  StunUInt32Attribute(uint16 type);
+
+#if (_MSC_VER < 1300)
+  enum { SIZE = 4 };
+#else
+  static const uint16 SIZE = 4;
+#endif
+
+  uint32 value() const { return bits_; }
+
+  void SetValue(uint32 bits) { bits_ = bits; }
+
+  bool GetBit(int index) const;
+  void SetBit(int index, bool value);
+
+  bool Read(talk_base::ByteBuffer* buf);
+  void Write(talk_base::ByteBuffer* buf) const;
+
+private:
+  uint32 bits_;
+};
+
+// Implements STUN/TURN attributs that record an arbitrary byte string
+class StunByteStringAttribute : public StunAttribute {
+public:
+  StunByteStringAttribute(uint16 type, uint16 length);
+  ~StunByteStringAttribute();
+
+  const char* bytes() const { return bytes_; }
+
+  void SetBytes(char* bytes, uint16 length);
+
+  void CopyBytes(const char* bytes); // uses strlen
+  void CopyBytes(const void* bytes, uint16 length);
+
+  uint8 GetByte(int index) const;
+  void SetByte(int index, uint8 value);
+
+  bool Read(talk_base::ByteBuffer* buf);
+  void Write(talk_base::ByteBuffer* buf) const;
+
+private:
+  char* bytes_;
+};
+
+// Implements STUN/TURN attributs that record an error code.
+class StunErrorCodeAttribute : public StunAttribute {
+public:
+  StunErrorCodeAttribute(uint16 type, uint16 length);
+  ~StunErrorCodeAttribute();
+
+#if (_MSC_VER < 1300)
+  enum { MIN_SIZE = 4 };
+#else
+  static const uint16 MIN_SIZE = 4;
+#endif
+
+  uint32 error_code() const { return (class_ << 8) | number_; }
+  uint8 error_class() const { return class_; }
+  uint8 number() const { return number_; }
+  const std::string& reason() const { return reason_; }
+
+  void SetErrorCode(uint32 code);
+  void SetErrorClass(uint8 eclass) { class_ = eclass; }
+  void SetNumber(uint8 number) { number_ = number; }
+  void SetReason(const std::string& reason);
+
+  bool Read(talk_base::ByteBuffer* buf);
+  void Write(talk_base::ByteBuffer* buf) const;
+
+private:
+  uint8 class_;
+  uint8 number_;
+  std::string reason_;
+};
+
+// Implements STUN/TURN attributs that record a list of attribute names.
+class StunUInt16ListAttribute : public StunAttribute {
+public:
+  StunUInt16ListAttribute(uint16 type, uint16 length);
+  ~StunUInt16ListAttribute();
+
+  size_t Size() const;
+  uint16 GetType(int index) const;
+  void SetType(int index, uint16 value);
+  void AddType(uint16 value);
+
+  bool Read(talk_base::ByteBuffer* buf);
+  void Write(talk_base::ByteBuffer* buf) const;
+
+private:
+  std::vector<uint16>* attr_types_;
+};
+
+// Implements the TURN TRANSPORT-PREFS attribute, which provides information
+// about the ports to allocate.
+class StunTransportPrefsAttribute : public StunAttribute {
+public:
+  StunTransportPrefsAttribute(uint16 type, uint16 length);
+  ~StunTransportPrefsAttribute();
+
+#if (_MSC_VER < 1300)
+  enum { SIZE1 = 4, SIZE2 = 12 };
+#else
+  static const uint16 SIZE1 = 4;
+  static const uint16 SIZE2 = 12;
+#endif
+
+  bool preallocate() const { return preallocate_; }
+  uint8 preference_type() const { return prefs_; }
+  const StunAddressAttribute* address() const { return addr_; }
+
+  void SetPreferenceType(uint8 prefs) { prefs_ = prefs; }
+
+  // Sets the preallocate address to the given value, or if 0 is given, it sets
+  // to not preallocate.
+  void SetPreallocateAddress(StunAddressAttribute* addr);
+
+  bool Read(talk_base::ByteBuffer* buf);
+  void Write(talk_base::ByteBuffer* buf) const;
+
+private:
+  bool preallocate_;
+  uint8 prefs_;
+  StunAddressAttribute* addr_;
+};
+
+// The special MAGIC-COOKIE attribute is used to distinguish TURN packets from
+// other kinds of traffic.
+const char STUN_MAGIC_COOKIE_VALUE[] = { 0x72, char(0xc6), 0x4b, char(0xc6) };
+
+// Returns the (successful) response type for the given request type.
+StunMessageType GetStunResponseType(StunMessageType request_type);
+
+// Returns the error response type for the given request type.
+StunMessageType GetStunErrorResponseType(StunMessageType request_type);
+
+} // namespace cricket
+
+#endif // __STUN_H__
diff --git a/talk/p2p/base/stunport.cc b/talk/p2p/base/stunport.cc
new file mode 100644
index 0000000..aa6e1d3
--- /dev/null
+++ b/talk/p2p/base/stunport.cc
@@ -0,0 +1,255 @@
+/*
+ * 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/p2p/base/stunport.h"
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/helpers.h"
+#include "talk/base/nethelpers.h"
+#include "talk/p2p/base/common.h"
+
+namespace cricket {
+
+// TODO: Move these to a common place (used in relayport too)
+const int KEEPALIVE_DELAY = 10 * 1000;  // 10 seconds - sort timeouts
+const int RETRY_DELAY = 50;             // 50ms, from ICE spec
+const int RETRY_TIMEOUT = 50 * 1000;    // ICE says 50 secs
+
+// Handles a binding request sent to the STUN server.
+class StunPortBindingRequest : public StunRequest {
+ public:
+  StunPortBindingRequest(StunPort* port, bool keep_alive,
+                         const talk_base::SocketAddress& addr)
+    : port_(port), keep_alive_(keep_alive), server_addr_(addr) {
+    start_time_ = talk_base::Time();
+  }
+
+  virtual ~StunPortBindingRequest() {
+  }
+
+  const talk_base::SocketAddress& server_addr() const { return server_addr_; }
+
+  virtual void Prepare(StunMessage* request) {
+    request->SetType(STUN_BINDING_REQUEST);
+  }
+
+  virtual void OnResponse(StunMessage* response) {
+    const StunAddressAttribute* addr_attr =
+        response->GetAddress(STUN_ATTR_MAPPED_ADDRESS);
+    if (!addr_attr) {
+      LOG(LS_ERROR) << "Binding response missing mapped address.";
+    } else if (addr_attr->family() != 1) {
+      LOG(LS_ERROR) << "Binding address has bad family";
+    } else {
+      talk_base::SocketAddress addr(addr_attr->ip(), addr_attr->port());
+      port_->AddAddress(addr, "udp", true);
+    }
+
+    // We will do a keep-alive regardless of whether this request suceeds.
+    // This should have almost no impact on network usage.
+    if (keep_alive_) {
+      port_->requests_.SendDelayed(
+          new StunPortBindingRequest(port_, true, server_addr_),
+          KEEPALIVE_DELAY);
+    }
+  }
+
+  virtual void OnErrorResponse(StunMessage* response) {
+    const StunErrorCodeAttribute* attr = response->GetErrorCode();
+    if (!attr) {
+      LOG(LS_ERROR) << "Bad allocate response error code";
+    } else {
+      LOG(LS_ERROR) << "Binding error response:"
+                 << " class=" << attr->error_class()
+                 << " number=" << attr->number()
+                 << " reason='" << attr->reason() << "'";
+    }
+
+    port_->SignalAddressError(port_);
+
+    if (keep_alive_
+        && (talk_base::TimeSince(start_time_) <= RETRY_TIMEOUT)) {
+      port_->requests_.SendDelayed(
+          new StunPortBindingRequest(port_, true, server_addr_),
+          KEEPALIVE_DELAY);
+    }
+  }
+
+  virtual void OnTimeout() {
+    LOG(LS_ERROR) << "Binding request timed out from "
+                  << port_->socket_->GetLocalAddress(NULL).ToString()
+                  << " (" << port_->network()->name() << ")";
+
+    port_->SignalAddressError(port_);
+
+    if (keep_alive_
+        && (talk_base::TimeSince(start_time_) <= RETRY_TIMEOUT)) {
+      port_->requests_.SendDelayed(
+          new StunPortBindingRequest(port_, true, server_addr_),
+          RETRY_DELAY);
+    }
+  }
+
+ private:
+  StunPort* port_;
+  bool keep_alive_;
+  talk_base::SocketAddress server_addr_;
+  uint32 start_time_;
+};
+
+const std::string STUN_PORT_TYPE("stun");
+
+StunPort::StunPort(talk_base::Thread* thread,
+                   talk_base::PacketSocketFactory* factory,
+                   talk_base::Network* network,
+                   uint32 ip, int min_port, int max_port,
+                   const talk_base::SocketAddress& server_addr)
+    : Port(thread, STUN_PORT_TYPE, factory, network, ip, min_port, max_port),
+      server_addr_(server_addr),
+      requests_(thread),
+      socket_(NULL),
+      error_(0),
+      resolver_(NULL) {
+  requests_.SignalSendPacket.connect(this, &StunPort::OnSendPacket);
+}
+
+bool StunPort::Init() {
+  socket_ = factory_->CreateUdpSocket(
+      talk_base::SocketAddress(ip_, 0), min_port_, max_port_);
+  if (!socket_) {
+    LOG_J(LS_WARNING, this) << "UDP socket creation failed";
+    return false;
+  }
+  socket_->SignalReadPacket.connect(this, &StunPort::OnReadPacket);
+  return true;
+}
+
+StunPort::~StunPort() {
+  if (resolver_) {
+    resolver_->Destroy(false);
+  }
+  delete socket_;
+}
+
+void StunPort::PrepareAddress() {
+  // We will keep pinging the stun server to make sure our NAT pin-hole stays
+  // open during the call.
+  if (server_addr_.IsUnresolved()) {
+    ResolveStunAddress();
+  } else {
+    requests_.Send(new StunPortBindingRequest(this, true, server_addr_));
+  }
+}
+
+void StunPort::PrepareSecondaryAddress() {
+  // DNS resolution of the secondary address is not currently supported.
+  ASSERT(!server_addr2_.IsAny());
+  requests_.Send(new StunPortBindingRequest(this, false, server_addr2_));
+}
+
+Connection* StunPort::CreateConnection(const Candidate& address,
+                                       CandidateOrigin origin) {
+  if (address.protocol() != "udp")
+    return NULL;
+
+  Connection* conn = new ProxyConnection(this, 0, address);
+  AddConnection(conn);
+  return conn;
+}
+
+int StunPort::SendTo(const void* data, size_t size,
+                     const talk_base::SocketAddress& addr, bool payload) {
+  int sent = socket_->SendTo(data, size, addr);
+  if (sent < 0) {
+    error_ = socket_->GetError();
+    LOG_J(LS_ERROR, this) << "UDP send of " << size
+                          << " bytes failed with error " << error_;
+  }
+  return sent;
+}
+
+int StunPort::SetOption(talk_base::Socket::Option opt, int value) {
+  return socket_->SetOption(opt, value);
+}
+
+int StunPort::GetError() {
+  return error_;
+}
+
+void StunPort::OnReadPacket(talk_base::AsyncPacketSocket* socket,
+                            const char* data, size_t size,
+                            const talk_base::SocketAddress& remote_addr) {
+  ASSERT(socket == socket_);
+
+  // Look for a response from the STUN server.
+  // Even if the response doesn't match one of our outstanding requests, we
+  // will eat it because it might be a response to a retransmitted packet, and
+  // we already cleared the request when we got the first response.
+  ASSERT(!server_addr_.IsUnresolved());
+  if (remote_addr == server_addr_ || remote_addr == server_addr2_) {
+    requests_.CheckResponse(data, size);
+    return;
+  }
+
+  if (Connection* conn = GetConnection(remote_addr)) {
+    conn->OnReadPacket(data, size);
+  } else {
+    Port::OnReadPacket(data, size, remote_addr);
+  }
+}
+
+void StunPort::ResolveStunAddress() {
+  if (resolver_)
+    return;
+
+  resolver_ = new talk_base::AsyncResolver();
+  resolver_->SignalWorkDone.connect(this, &StunPort::OnResolveResult);
+  resolver_->set_address(server_addr_);
+  resolver_->Start();
+}
+
+void StunPort::OnResolveResult(talk_base::SignalThread* t) {
+  ASSERT(t == resolver_);
+  if (resolver_->error() != 0) {
+    LOG_J(LS_WARNING, this) << "StunPort: stun host lookup received error "
+                            << resolver_->error();
+    SignalAddressError(this);
+  }
+
+  server_addr_ = resolver_->address();
+  PrepareAddress();
+}
+
+// TODO: merge this with SendTo above.
+void StunPort::OnSendPacket(const void* data, size_t size, StunRequest* req) {
+  StunPortBindingRequest* sreq = static_cast<StunPortBindingRequest*>(req);
+  if (socket_->SendTo(data, size, sreq->server_addr()) < 0)
+    PLOG(LERROR, socket_->GetError()) << "sendto";
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/stunport.h b/talk/p2p/base/stunport.h
new file mode 100644
index 0000000..0b41724
--- /dev/null
+++ b/talk/p2p/base/stunport.h
@@ -0,0 +1,117 @@
+/*
+ * 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_P2P_BASE_STUNPORT_H_
+#define TALK_P2P_BASE_STUNPORT_H_
+
+#include <string>
+
+#include "talk/base/asyncpacketsocket.h"
+#include "talk/p2p/base/udpport.h"
+#include "talk/p2p/base/stunrequest.h"
+
+namespace talk_base {
+class AsyncResolver;
+class SignalThread;
+}
+
+namespace cricket {
+
+extern const std::string STUN_PORT_TYPE;
+
+// Communicates using the address on the outside of a NAT.
+class StunPort : public Port {
+ public:
+  static StunPort* Create(talk_base::Thread* thread,
+                          talk_base::PacketSocketFactory* factory,
+                          talk_base::Network* network,
+                          uint32 ip, int min_port, int max_port,
+                          const talk_base::SocketAddress& server_addr) {
+    StunPort* port = new StunPort(thread, factory, network,
+                                  ip, min_port, max_port, server_addr);
+    if (!port->Init()) {
+      delete port;
+      port = NULL;
+    }
+    return port;
+  }
+  virtual ~StunPort();
+
+  const talk_base::SocketAddress& server_addr() const { return server_addr_; }
+  void set_server_addr(const talk_base::SocketAddress& addr) {
+    server_addr_ = addr;
+  }
+
+  const talk_base::SocketAddress& server_addr2() const { return server_addr2_; }
+  void set_server_addr2(const talk_base::SocketAddress& addr) {
+    server_addr2_ = addr;
+  }
+
+  virtual void PrepareAddress();
+
+  // This will contact the secondary server and signal another candidate
+  // address for this port (which may be the same as the first address).
+  void PrepareSecondaryAddress();
+
+  virtual Connection* CreateConnection(const Candidate& address,
+                                       CandidateOrigin origin);
+  virtual int SetOption(talk_base::Socket::Option opt, int value);
+  virtual int GetError();
+
+ protected:
+  StunPort(talk_base::Thread* thread, talk_base::PacketSocketFactory* factory,
+           talk_base::Network* network, uint32 ip, int min_port, int max_port,
+           const talk_base::SocketAddress& server_addr);
+  bool Init();
+
+  virtual int SendTo(const void* data, size_t size,
+                     const talk_base::SocketAddress& addr, bool payload);
+
+  void OnReadPacket(talk_base::AsyncPacketSocket* socket,
+                    const char* data, size_t size,
+                    const talk_base::SocketAddress& remote_addr);
+
+ private:
+  // DNS resolution of the STUN server.
+  void ResolveStunAddress();
+  void OnResolveResult(talk_base::SignalThread* thread);
+  // Sends STUN requests to the server.
+  void OnSendPacket(const void* data, size_t size, StunRequest* req);
+
+  talk_base::SocketAddress server_addr_;
+  talk_base::SocketAddress server_addr2_;
+  StunRequestManager requests_;
+  talk_base::AsyncPacketSocket* socket_;
+  int error_;
+  talk_base::AsyncResolver* resolver_;
+
+  friend class StunPortBindingRequest;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_STUNPORT_H_
diff --git a/talk/p2p/base/stunrequest.cc b/talk/p2p/base/stunrequest.cc
new file mode 100644
index 0000000..1ad121e
--- /dev/null
+++ b/talk/p2p/base/stunrequest.cc
@@ -0,0 +1,200 @@
+/*
+ * 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/p2p/base/stunrequest.h"
+
+#include "talk/base/common.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+
+namespace cricket {
+
+const uint32 MSG_STUN_SEND = 1;
+
+const int MAX_SENDS = 9;
+const int DELAY_UNIT = 100;  // 100 milliseconds
+const int DELAY_MAX_FACTOR = 16;
+
+StunRequestManager::StunRequestManager(talk_base::Thread* thread)
+    : thread_(thread) {
+}
+
+StunRequestManager::~StunRequestManager() {
+  while (requests_.begin() != requests_.end()) {
+    StunRequest *request = requests_.begin()->second;
+    requests_.erase(requests_.begin());
+    delete request;
+  }
+}
+
+void StunRequestManager::Send(StunRequest* request) {
+  SendDelayed(request, 0);
+}
+
+void StunRequestManager::SendDelayed(StunRequest* request, int delay) {
+  request->set_manager(this);
+  ASSERT(requests_.find(request->id()) == requests_.end());
+  request->Construct();
+  requests_[request->id()] = request;
+  thread_->PostDelayed(delay, request, MSG_STUN_SEND, NULL);
+}
+
+void StunRequestManager::Remove(StunRequest* request) {
+  ASSERT(request->manager() == this);
+  RequestMap::iterator iter = requests_.find(request->id());
+  if (iter != requests_.end()) {
+    ASSERT(iter->second == request);
+    requests_.erase(iter);
+    thread_->Clear(request);
+  }
+}
+
+void StunRequestManager::Clear() {
+  std::vector<StunRequest*> requests;
+  for (RequestMap::iterator i = requests_.begin(); i != requests_.end(); ++i)
+    requests.push_back(i->second);
+
+  for (uint32 i = 0; i < requests.size(); ++i)
+    Remove(requests[i]);
+}
+
+bool StunRequestManager::CheckResponse(StunMessage* msg) {
+  RequestMap::iterator iter = requests_.find(msg->transaction_id());
+  if (iter == requests_.end())
+    return false;
+
+  StunRequest* request = iter->second;
+  if (msg->type() == GetStunResponseType(request->type())) {
+    request->OnResponse(msg);
+  } else if (msg->type() == GetStunErrorResponseType(request->type())) {
+    request->OnErrorResponse(msg);
+  } else {
+    LOG(LERROR) << "Received response with wrong type: " << msg->type()
+               << " (expecting " << GetStunResponseType(request->type()) << ")";
+    return false;
+  }
+
+  delete request;
+  return true;
+}
+
+bool StunRequestManager::CheckResponse(const char* data, size_t size) {
+  // Check the appropriate bytes of the stream to see if they match the
+  // transaction ID of a response we are expecting.
+
+  if (size < 20)
+    return false;
+
+  std::string id;
+  id.append(data + 4, 16);
+
+  RequestMap::iterator iter = requests_.find(id);
+  if (iter == requests_.end())
+    return false;
+
+  // Parse the STUN message and continue processing as usual.
+
+  talk_base::ByteBuffer buf(data, size);
+  StunMessage msg;
+  if (!msg.Read(&buf))
+    return false;
+
+  return CheckResponse(&msg);
+}
+
+StunRequest::StunRequest()
+    : count_(0), timeout_(false), manager_(0),
+      id_(talk_base::CreateRandomString(16)), msg_(new StunMessage()),
+      tstamp_(0) {
+  msg_->SetTransactionID(id_);
+}
+
+StunRequest::StunRequest(StunMessage* request)
+  : count_(0), timeout_(false), manager_(0),
+    id_(request->transaction_id()), msg_(request) {
+}
+
+StunRequest::~StunRequest() {
+  ASSERT(manager_ != NULL);
+  if (manager_) {
+    manager_->Remove(this);
+    manager_->thread_->Clear(this);
+  }
+  delete msg_;
+}
+
+void StunRequest::Construct() {
+  if (msg_->type() == 0) {
+    Prepare(msg_);
+    ASSERT(msg_->transaction_id() == id_);
+    ASSERT(msg_->type() != 0);
+  }
+}
+
+StunMessageType StunRequest::type() {
+  ASSERT(msg_ != NULL);
+  return msg_->type();
+}
+
+void StunRequest::set_manager(StunRequestManager* manager) {
+  ASSERT(!manager_);
+  manager_ = manager;
+}
+
+void StunRequest::OnMessage(talk_base::Message* pmsg) {
+  ASSERT(manager_ != NULL);
+  ASSERT(pmsg->message_id == MSG_STUN_SEND);
+
+  if (timeout_) {
+    OnTimeout();
+    delete this;
+    return;
+  }
+
+  tstamp_ = talk_base::Time();
+
+  talk_base::ByteBuffer buf;
+  msg_->Write(&buf);
+  manager_->SignalSendPacket(buf.Data(), buf.Length(), this);
+
+  int delay = GetNextDelay();
+  manager_->thread_->PostDelayed(delay, this, MSG_STUN_SEND, NULL);
+}
+
+uint32 StunRequest::Elapsed() const {
+  return talk_base::TimeSince(tstamp_);
+}
+
+int StunRequest::GetNextDelay() {
+  int delay = DELAY_UNIT * talk_base::_min(1 << count_, DELAY_MAX_FACTOR);
+  count_ += 1;
+  if (count_ == MAX_SENDS)
+    timeout_ = true;
+  return delay;
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/stunrequest.h b/talk/p2p/base/stunrequest.h
new file mode 100644
index 0000000..fea2c99
--- /dev/null
+++ b/talk/p2p/base/stunrequest.h
@@ -0,0 +1,129 @@
+/*
+ * 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_P2P_BASE_STUNREQUEST_H_
+#define TALK_P2P_BASE_STUNREQUEST_H_
+
+#include "talk/base/sigslot.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/stun.h"
+#include <map>
+#include <string>
+
+namespace cricket {
+
+class StunRequest;
+
+// Manages a set of STUN requests, sending and resending until we receive a
+// response or determine that the request has timed out.
+class StunRequestManager {
+public:
+  StunRequestManager(talk_base::Thread* thread);
+  ~StunRequestManager();
+
+  // Starts sending the given request (perhaps after a delay).
+  void Send(StunRequest* request);
+  void SendDelayed(StunRequest* request, int delay);
+
+  // Removes a stun request that was added previously.  This will happen
+  // automatically when a request succeeds, fails, or times out.
+  void Remove(StunRequest* request);
+
+  // Removes all stun requests that were added previously.
+  void Clear();
+
+  // Determines whether the given message is a response to one of the
+  // outstanding requests, and if so, processes it appropriately.
+  bool CheckResponse(StunMessage* msg);
+  bool CheckResponse(const char* data, size_t size);
+
+  // Raised when there are bytes to be sent.
+  sigslot::signal3<const void*, size_t, StunRequest*> SignalSendPacket;
+
+private:
+  typedef std::map<std::string, StunRequest*> RequestMap;
+
+  talk_base::Thread* thread_;
+  RequestMap requests_;
+
+  friend class StunRequest;
+};
+
+// Represents an individual request to be sent.  The STUN message can either be
+// constructed beforehand or built on demand.
+class StunRequest : public talk_base::MessageHandler {
+public:
+  StunRequest();
+  StunRequest(StunMessage* request);
+  virtual ~StunRequest();
+
+  // Causes our wrapped StunMessage to be Prepared
+  void Construct();
+
+  // The manager handling this request (if it has been scheduled for sending).
+  StunRequestManager* manager() { return manager_; }
+
+  // Returns the transaction ID of this request.
+  const std::string& id() { return id_; }
+
+  // Returns the STUN type of the request message.
+  StunMessageType type();
+
+  // Handles messages for sending and timeout.
+  void OnMessage(talk_base::Message* pmsg);
+
+  // Time elapsed since last send (in ms)
+  uint32 Elapsed() const;
+
+protected:
+  int count_;
+  bool timeout_;
+
+  // Fills in a request object to be sent.  Note that request's transaction ID
+  // will already be set and cannot be changed.
+  virtual void Prepare(StunMessage* request) {}
+
+  // Called when the message receives a response or times out.
+  virtual void OnResponse(StunMessage* response) {}
+  virtual void OnErrorResponse(StunMessage* response) {}
+  virtual void OnTimeout() {}
+  virtual int GetNextDelay();
+
+private:
+  StunRequestManager* manager_;
+  std::string id_;
+  StunMessage* msg_;
+  uint32 tstamp_;
+
+  void set_manager(StunRequestManager* manager);
+
+  friend class StunRequestManager;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_STUNREQUEST_H_
diff --git a/talk/p2p/base/stunserver.cc b/talk/p2p/base/stunserver.cc
new file mode 100644
index 0000000..a0686a1
--- /dev/null
+++ b/talk/p2p/base/stunserver.cc
@@ -0,0 +1,164 @@
+/*
+ * 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.
+ */
+
+#ifdef POSIX
+#include <errno.h>
+#endif  // POSIX
+
+#include "talk/p2p/base/stunserver.h"
+#include "talk/base/bytebuffer.h"
+#include "talk/base/logging.h"
+
+namespace cricket {
+
+StunServer::StunServer(talk_base::AsyncUDPSocket* socket) : socket_(socket) {
+  socket_->SignalReadPacket.connect(this, &StunServer::OnPacket);
+}
+
+StunServer::~StunServer() {
+  socket_->SignalReadPacket.disconnect(this);
+}
+
+void StunServer::OnPacket(
+    talk_base::AsyncPacketSocket* socket, const char* buf, size_t size,
+    const talk_base::SocketAddress& remote_addr) {
+
+  // TODO: If appropriate, look for the magic cookie before parsing.
+
+  // Parse the STUN message.
+  talk_base::ByteBuffer bbuf(buf, size);
+  StunMessage msg;
+  if (!msg.Read(&bbuf)) {
+    SendErrorResponse(msg, remote_addr, 400, "Bad Request");
+    return;
+  }
+
+  // TODO: If this is UDP, then we shouldn't allow non-fully-parsed messages.
+
+  // TODO: If unknown non-optiional (<= 0x7fff) attributes are found, send a
+  //       420 "Unknown Attribute" response.
+
+  // TODO: Check that a message-integrity attribute was given (or send 401
+  //       "Unauthorized").  Check that a username attribute was given (or send
+  //       432 "Missing Username").  Look up the username and password.  If it
+  //       is missing or the HMAC is wrong, send 431 "Integrity Check Failure".
+
+  // Send the message to the appropriate handler function.
+  switch (msg.type()) {
+  case STUN_BINDING_REQUEST:
+    OnBindingRequest(&msg, remote_addr);
+    return;
+
+  case STUN_ALLOCATE_REQUEST:
+    OnAllocateRequest(&msg, remote_addr);
+    return;
+
+  default:
+    SendErrorResponse(msg, remote_addr, 600, "Operation Not Supported");
+  }
+}
+
+void StunServer::OnBindingRequest(
+    StunMessage* msg, const talk_base::SocketAddress& remote_addr) {
+  StunMessage response;
+  response.SetType(STUN_BINDING_RESPONSE);
+  response.SetTransactionID(msg->transaction_id());
+
+  // Tell the user the address that we received their request from.
+  StunAddressAttribute* mapped_addr =
+      StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS);
+  mapped_addr->SetFamily(1);
+  mapped_addr->SetPort(remote_addr.port());
+  mapped_addr->SetIP(remote_addr.ip());
+  response.AddAttribute(mapped_addr);
+
+  // Tell the user the address that we are sending the response from.
+  // This method should not be called if socket address is not
+  // allocated yet.
+  bool allocated;
+  talk_base::SocketAddress local_addr = socket_->GetLocalAddress(&allocated);
+  ASSERT(allocated);
+
+  StunAddressAttribute* source_addr =
+      StunAttribute::CreateAddress(STUN_ATTR_SOURCE_ADDRESS);
+  source_addr->SetFamily(1);
+  source_addr->SetPort(local_addr.port());
+  source_addr->SetIP(local_addr.ip());
+  response.AddAttribute(source_addr);
+
+  // TODO: Add username and message-integrity.
+
+  // TODO: Add changed-address.  (Keep information about three other servers.)
+
+  SendResponse(response, remote_addr);
+}
+
+void StunServer::OnAllocateRequest(
+    StunMessage* msg, const talk_base::SocketAddress& addr) {
+  SendErrorResponse(*msg, addr, 600, "Operation Not Supported");
+}
+
+void StunServer::OnSharedSecretRequest(
+    StunMessage* msg, const talk_base::SocketAddress& addr) {
+  SendErrorResponse(*msg, addr, 600, "Operation Not Supported");
+}
+
+void StunServer::OnSendRequest(StunMessage* msg,
+                               const talk_base::SocketAddress& addr) {
+  SendErrorResponse(*msg, addr, 600, "Operation Not Supported");
+}
+
+void StunServer::SendErrorResponse(
+    const StunMessage& msg, const talk_base::SocketAddress& addr,
+    int error_code, const char* error_desc) {
+
+  StunMessage err_msg;
+  err_msg.SetType(GetStunErrorResponseType(msg.type()));
+  err_msg.SetTransactionID(msg.transaction_id());
+
+  StunErrorCodeAttribute* err_code = StunAttribute::CreateErrorCode();
+  err_code->SetErrorClass(error_code / 100);
+  err_code->SetNumber(error_code % 100);
+  err_code->SetReason(error_desc);
+  err_msg.AddAttribute(err_code);
+
+  SendResponse(err_msg, addr);
+}
+
+void StunServer::SendResponse(
+    const StunMessage& msg, const talk_base::SocketAddress& addr) {
+
+  talk_base::ByteBuffer buf;
+  msg.Write(&buf);
+
+  // TODO: Allow response addr attribute if sent from another stun server.
+
+  if (socket_->SendTo(buf.Data(), buf.Length(), addr) < 0)
+    LOG_ERR(LS_ERROR) << "sendto";
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/stunserver.h b/talk/p2p/base/stunserver.h
new file mode 100644
index 0000000..6e51ad1
--- /dev/null
+++ b/talk/p2p/base/stunserver.h
@@ -0,0 +1,77 @@
+/*
+ * 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_P2P_BASE_STUNSERVER_H_
+#define TALK_P2P_BASE_STUNSERVER_H_
+
+#include "talk/base/asyncudpsocket.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/p2p/base/stun.h"
+
+namespace cricket {
+
+const int STUN_SERVER_PORT = 3478;
+
+class StunServer : public sigslot::has_slots<> {
+ public:
+  // Creates a STUN server, which will listen on the given socket.
+  explicit StunServer(talk_base::AsyncUDPSocket* socket);
+  // Removes the STUN server from the socket and deletes the socket.
+  ~StunServer();
+
+ protected:
+  // Slot for AsyncSocket.PacketRead:
+  void OnPacket(
+      talk_base::AsyncPacketSocket* socket, const char* buf, size_t size,
+      const talk_base::SocketAddress& remote_addr);
+
+  // Handlers for the different types of STUN/TURN requests:
+  void OnBindingRequest(StunMessage* msg,
+      const talk_base::SocketAddress& addr);
+  void OnAllocateRequest(StunMessage* msg,
+      const talk_base::SocketAddress& addr);
+  void OnSharedSecretRequest(StunMessage* msg,
+      const talk_base::SocketAddress& addr);
+  void OnSendRequest(StunMessage* msg,
+      const talk_base::SocketAddress& addr);
+
+  // Sends an error response to the given message back to the user.
+  void SendErrorResponse(
+      const StunMessage& msg, const talk_base::SocketAddress& addr,
+      int error_code, const char* error_desc);
+
+  // Sends the given message to the appropriate destination.
+  void SendResponse(const StunMessage& msg,
+       const talk_base::SocketAddress& addr);
+
+ private:
+  talk_base::scoped_ptr<talk_base::AsyncUDPSocket> socket_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_STUNSERVER_H_
diff --git a/talk/p2p/base/stunserver_main.cc b/talk/p2p/base/stunserver_main.cc
new file mode 100644
index 0000000..e697728
--- /dev/null
+++ b/talk/p2p/base/stunserver_main.cc
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ */
+
+#ifdef POSIX
+#include <errno.h>
+#endif  // POSIX
+
+#include <iostream>
+
+#include "talk/base/host.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/stunserver.h"
+
+using namespace cricket;
+
+int main(int argc, char* argv[]) {
+  if (argc != 2) {
+    std::cerr << "usage: stunserver address" << std::endl;
+    return 1;
+  }
+
+  talk_base::SocketAddress server_addr;
+  if (!server_addr.FromString(argv[1])) {
+    std::cerr << "Unable to parse IP address: " << argv[1];
+    return 1;
+  }
+
+  talk_base::Thread *pthMain = talk_base::Thread::Current();
+
+  talk_base::AsyncUDPSocket* server_socket =
+      talk_base::AsyncUDPSocket::Create(pthMain->socketserver(), server_addr);
+  if (!server_socket) {
+    std::cerr << "Failed to create a UDP socket" << std::endl;
+    return 1;
+  }
+
+  StunServer* server = new StunServer(server_socket);
+
+  std::cout << "Listening at " << server_addr.ToString() << std::endl;
+
+  pthMain->Run();
+
+  delete server;
+  return 0;
+}
diff --git a/talk/p2p/base/tcpport.cc b/talk/p2p/base/tcpport.cc
new file mode 100644
index 0000000..41e04d2
--- /dev/null
+++ b/talk/p2p/base/tcpport.cc
@@ -0,0 +1,262 @@
+/*
+ * 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/p2p/base/tcpport.h"
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/p2p/base/common.h"
+
+namespace cricket {
+
+TCPPort::TCPPort(talk_base::Thread* thread,
+                 talk_base::PacketSocketFactory* factory,
+                 talk_base::Network* network, uint32 ip,
+                 int min_port, int max_port, bool allow_listen)
+    : Port(thread, LOCAL_PORT_TYPE, factory, network, ip, min_port, max_port),
+      incoming_only_(false),
+      allow_listen_(allow_listen),
+      socket_(NULL),
+      error_(0) {
+}
+
+bool TCPPort::Init() {
+  // Treat failure to create or bind a TCP socket as fatal.  This
+  // should never happen.
+  socket_ = factory_->CreateServerTcpSocket(
+      talk_base::SocketAddress(ip_, 0), min_port_, max_port_, allow_listen_,
+      false /* ssl */);
+  if (!socket_) {
+    LOG_J(LS_ERROR, this) << "TCP socket creation failed.";
+    return false;
+  }
+  socket_->SignalNewConnection.connect(this, &TCPPort::OnNewConnection);
+  return true;
+}
+
+TCPPort::~TCPPort() {
+  delete socket_;
+}
+
+Connection* TCPPort::CreateConnection(const Candidate& address,
+                                      CandidateOrigin origin) {
+  // We only support TCP protocols
+  if ((address.protocol() != "tcp") && (address.protocol() != "ssltcp"))
+    return NULL;
+
+  // We can't accept TCP connections incoming on other ports
+  if (origin == ORIGIN_OTHER_PORT)
+    return NULL;
+
+  // Check if we are allowed to make outgoing TCP connections
+  if (incoming_only_ && (origin == ORIGIN_MESSAGE))
+    return NULL;
+
+  // We don't know how to act as an ssl server yet
+  if ((address.protocol() == "ssltcp") && (origin == ORIGIN_THIS_PORT))
+    return NULL;
+
+  TCPConnection* conn = NULL;
+  if (talk_base::AsyncPacketSocket* socket =
+      GetIncoming(address.address(), true)) {
+    socket->SignalReadPacket.disconnect(this);
+    conn = new TCPConnection(this, address, socket);
+  } else {
+    conn = new TCPConnection(this, address);
+  }
+  AddConnection(conn);
+  return conn;
+}
+
+void TCPPort::PrepareAddress() {
+  if (!allow_listen_) {
+    LOG_J(LS_INFO, this) << "Not listening due to firewall restrictions.";
+  }
+  // Note: We still add the address, since otherwise the remote side won't
+  // recognize our incoming TCP connections.
+  bool allocated;
+  talk_base::SocketAddress address = socket_->GetLocalAddress(&allocated);
+  if (allocated) {
+    AddAddress(address, "tcp", true);
+  } else {
+    socket_->SignalAddressReady.connect(this, &TCPPort::OnAddresReady);
+  }
+}
+
+int TCPPort::SendTo(const void* data, size_t size,
+                    const talk_base::SocketAddress& addr, bool payload) {
+  talk_base::AsyncPacketSocket * socket = NULL;
+  if (TCPConnection * conn = static_cast<TCPConnection*>(GetConnection(addr))) {
+    socket = conn->socket();
+  } else {
+    socket = GetIncoming(addr);
+  }
+  if (!socket) {
+    LOG_J(LS_ERROR, this) << "Attempted to send to an unknown destination, "
+                          << addr.ToString();
+    return -1;  // TODO: Set error_
+  }
+
+  int sent = socket->Send(data, size);
+  if (sent < 0) {
+    error_ = socket->GetError();
+    LOG_J(LS_ERROR, this) << "TCP send of " << size
+                          << " bytes failed with error " << error_;
+  }
+  return sent;
+}
+
+int TCPPort::SetOption(talk_base::Socket::Option opt, int value) {
+  return socket_->SetOption(opt, value);
+}
+
+int TCPPort::GetError() {
+  return error_;
+}
+
+void TCPPort::OnNewConnection(talk_base::AsyncPacketSocket* socket,
+                              talk_base::AsyncPacketSocket* new_socket) {
+  ASSERT(socket == socket_);
+
+  Incoming incoming;
+  incoming.addr = new_socket->GetRemoteAddress();
+  incoming.socket = new_socket;
+  incoming.socket->SignalReadPacket.connect(this, &TCPPort::OnReadPacket);
+
+  LOG_J(LS_VERBOSE, this) << "Accepted connection from "
+                          << incoming.addr.ToString();
+  incoming_.push_back(incoming);
+}
+
+talk_base::AsyncPacketSocket* TCPPort::GetIncoming(
+    const talk_base::SocketAddress& addr, bool remove) {
+  talk_base::AsyncPacketSocket* socket = NULL;
+  for (std::list<Incoming>::iterator it = incoming_.begin();
+       it != incoming_.end(); ++it) {
+    if (it->addr == addr) {
+      socket = it->socket;
+      if (remove)
+        incoming_.erase(it);
+      break;
+    }
+  }
+  return socket;
+}
+
+void TCPPort::OnReadPacket(talk_base::AsyncPacketSocket* socket,
+                           const char* data, size_t size,
+                           const talk_base::SocketAddress& remote_addr) {
+  Port::OnReadPacket(data, size, remote_addr);
+}
+
+void TCPPort::OnAddresReady(talk_base::AsyncPacketSocket* socket,
+                            const talk_base::SocketAddress& address) {
+  AddAddress(address, "tcp", true);
+}
+
+TCPConnection::TCPConnection(TCPPort* port, const Candidate& candidate,
+                             talk_base::AsyncPacketSocket* socket)
+    : Connection(port, 0, candidate), socket_(socket), error_(0) {
+  bool outgoing = (socket_ == NULL);
+  if (outgoing) {
+    // TODO: Handle failures here (unlikely since TCP).
+
+    socket_ = port->socket_factory()->CreateClientTcpSocket(
+        talk_base::SocketAddress(port_->network()->ip(), 0),
+        candidate.address(), port->proxy(), port->user_agent(),
+        candidate.protocol() == "ssltcp");
+    if (socket_) {
+      LOG_J(LS_VERBOSE, this) << "Connecting from "
+                              << socket_->GetLocalAddress(NULL).ToString()
+                              << " to " << candidate.address().ToString();
+      set_connected(false);
+      socket_->SignalConnect.connect(this, &TCPConnection::OnConnect);
+    } else {
+      LOG_J(LS_WARNING, this) << "Failed to create connection to "
+                              << candidate.address().ToString();
+    }
+  } else {
+    // Incoming connections should match the network address.
+    ASSERT(socket_->GetLocalAddress(NULL).ip() == port->ip_);
+  }
+
+  if (socket_) {
+    socket_->SignalReadPacket.connect(this, &TCPConnection::OnReadPacket);
+    socket_->SignalClose.connect(this, &TCPConnection::OnClose);
+  }
+}
+
+TCPConnection::~TCPConnection() {
+  delete socket_;
+}
+
+int TCPConnection::Send(const void* data, size_t size) {
+  if (!socket_) {
+    error_ = ENOTCONN;
+    return SOCKET_ERROR;
+  }
+
+  if (write_state() != STATE_WRITABLE) {
+    // TODO: Should STATE_WRITE_TIMEOUT return a non-blocking error?
+    error_ = EWOULDBLOCK;
+    return SOCKET_ERROR;
+  }
+  int sent = socket_->Send(data, size);
+  if (sent < 0) {
+    error_ = socket_->GetError();
+  } else {
+    send_rate_tracker_.Update(sent);
+  }
+  return sent;
+}
+
+int TCPConnection::GetError() {
+  return error_;
+}
+
+void TCPConnection::OnConnect(talk_base::AsyncPacketSocket* socket) {
+  ASSERT(socket == socket_);
+  LOG_J(LS_VERBOSE, this) << "Connection established to "
+                          << socket->GetRemoteAddress().ToString();
+  set_connected(true);
+}
+
+void TCPConnection::OnClose(talk_base::AsyncPacketSocket* socket, int error) {
+  ASSERT(socket == socket_);
+  LOG_J(LS_VERBOSE, this) << "Connection closed with error " << error;
+  set_connected(false);
+  set_write_state(STATE_WRITE_TIMEOUT);
+}
+
+void TCPConnection::OnReadPacket(talk_base::AsyncPacketSocket* socket,
+                                 const char* data, size_t size,
+                                 const talk_base::SocketAddress& remote_addr) {
+  ASSERT(socket == socket_);
+  Connection::OnReadPacket(data, size);
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/tcpport.h b/talk/p2p/base/tcpport.h
new file mode 100644
index 0000000..0de3561
--- /dev/null
+++ b/talk/p2p/base/tcpport.h
@@ -0,0 +1,141 @@
+/*
+ * 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_P2P_BASE_TCPPORT_H_
+#define TALK_P2P_BASE_TCPPORT_H_
+
+#include <string>
+#include <list>
+#include "talk/base/asyncpacketsocket.h"
+#include "talk/p2p/base/port.h"
+
+namespace cricket {
+
+class TCPConnection;
+
+extern const std::string LOCAL_PORT_TYPE;  // type of TCP ports
+
+// Communicates using a local TCP port.
+//
+// This class is designed to allow subclasses to take advantage of the
+// connection management provided by this class.  A subclass should take of all
+// packet sending and preparation, but when a packet is received, it should
+// call this TCPPort::OnReadPacket (3 arg) to dispatch to a connection.
+class TCPPort : public Port {
+ public:
+  static TCPPort* Create(talk_base::Thread* thread,
+                         talk_base::PacketSocketFactory* factory,
+                         talk_base::Network* network,
+                         uint32 ip, int min_port, int max_port,
+                         bool allow_listen) {
+    TCPPort* port = new TCPPort(thread, factory, network,
+                                ip, min_port, max_port, allow_listen);
+    if (!port->Init()) {
+      delete port;
+      port = NULL;
+    }
+    return port;
+  }
+  virtual ~TCPPort();
+
+  virtual Connection* CreateConnection(const Candidate& address,
+                                       CandidateOrigin origin);
+
+  virtual void PrepareAddress();
+
+  virtual int SetOption(talk_base::Socket::Option opt, int value);
+  virtual int GetError();
+
+ protected:
+  TCPPort(talk_base::Thread* thread, talk_base::PacketSocketFactory* factory,
+          talk_base::Network* network, uint32 ip, int min_port, int max_port,
+          bool allow_listen);
+  bool Init();
+
+  // Handles sending using the local TCP socket.
+  virtual int SendTo(const void* data, size_t size,
+                     const talk_base::SocketAddress& addr, bool payload);
+
+  // Accepts incoming TCP connection.
+  void OnNewConnection(talk_base::AsyncPacketSocket* socket,
+                       talk_base::AsyncPacketSocket* new_socket);
+
+ private:
+  struct Incoming {
+    talk_base::SocketAddress addr;
+    talk_base::AsyncPacketSocket* socket;
+  };
+
+  talk_base::AsyncPacketSocket* GetIncoming(
+      const talk_base::SocketAddress& addr, bool remove = false);
+
+  // Receives packet signal from the local TCP Socket.
+  void OnReadPacket(talk_base::AsyncPacketSocket* socket,
+                    const char* data, size_t size,
+                    const talk_base::SocketAddress& remote_addr);
+
+  void OnAddresReady(talk_base::AsyncPacketSocket* socket,
+                     const talk_base::SocketAddress& address);
+
+  // TODO: Is this still needed?
+  bool incoming_only_;
+  bool allow_listen_;
+  talk_base::AsyncPacketSocket* socket_;
+  int error_;
+  std::list<Incoming> incoming_;
+
+  friend class TCPConnection;
+};
+
+class TCPConnection : public Connection {
+ public:
+  // Connection is outgoing unless socket is specified
+  TCPConnection(TCPPort* port, const Candidate& candidate,
+                talk_base::AsyncPacketSocket* socket = 0);
+  virtual ~TCPConnection();
+
+  virtual int Send(const void* data, size_t size);
+  virtual int GetError();
+
+  talk_base::AsyncPacketSocket* socket() { return socket_; }
+
+ private:
+  void OnConnect(talk_base::AsyncPacketSocket* socket);
+  void OnClose(talk_base::AsyncPacketSocket* socket, int error);
+  void OnReadPacket(talk_base::AsyncPacketSocket* socket,
+                    const char* data, size_t size,
+                    const talk_base::SocketAddress& remote_addr);
+
+  talk_base::AsyncPacketSocket* socket_;
+  int error_;
+
+  friend class TCPPort;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_TCPPORT_H_
diff --git a/talk/p2p/base/transport.cc b/talk/p2p/base/transport.cc
new file mode 100644
index 0000000..dc571d4
--- /dev/null
+++ b/talk/p2p/base/transport.cc
@@ -0,0 +1,468 @@
+/*
+ * 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/p2p/base/transport.h"
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/p2p/base/candidate.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/sessionmanager.h"
+#include "talk/p2p/base/parsing.h"
+#include "talk/p2p/base/transportchannelimpl.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/constants.h"
+
+namespace cricket {
+
+struct ChannelParams {
+  ChannelParams() : channel(NULL), candidate(NULL) {}
+  explicit ChannelParams(const std::string& name)
+      : name(name), channel(NULL), candidate(NULL) {}
+  ChannelParams(const std::string& name,
+                const std::string& content_type)
+      : name(name), content_type(content_type),
+        channel(NULL), candidate(NULL) {}
+  explicit ChannelParams(cricket::Candidate* candidate) :
+      channel(NULL), candidate(candidate) {
+    name = candidate->name();
+  }
+
+  ~ChannelParams() {
+    delete candidate;
+  }
+
+  std::string name;
+  std::string content_type;
+  cricket::TransportChannelImpl* channel;
+  cricket::Candidate* candidate;
+};
+typedef talk_base::TypedMessageData<ChannelParams*> ChannelMessage;
+
+enum {
+  MSG_CREATECHANNEL = 1,
+  MSG_DESTROYCHANNEL = 2,
+  MSG_DESTROYALLCHANNELS = 3,
+  MSG_CONNECTCHANNELS = 4,
+  MSG_RESETCHANNELS = 5,
+  MSG_ONSIGNALINGREADY = 6,
+  MSG_ONREMOTECANDIDATE = 7,
+  MSG_READSTATE = 8,
+  MSG_WRITESTATE = 9,
+  MSG_REQUESTSIGNALING = 10,
+  MSG_ONCHANNELCANDIDATEREADY = 11,
+  MSG_CONNECTING = 12,
+};
+
+Transport::Transport(talk_base::Thread* signaling_thread,
+                     talk_base::Thread* worker_thread,
+                     const std::string& type,
+                     PortAllocator* allocator)
+  : signaling_thread_(signaling_thread),
+    worker_thread_(worker_thread), type_(type), allocator_(allocator),
+    destroyed_(false), readable_(false), writable_(false),
+    connect_requested_(false), allow_local_ips_(false) {
+}
+
+Transport::~Transport() {
+  ASSERT(signaling_thread_->IsCurrent());
+  ASSERT(destroyed_);
+}
+
+TransportChannelImpl* Transport::CreateChannel(
+    const std::string& name, const std::string& content_type) {
+  ChannelParams params(name, content_type);
+  ChannelMessage msg(&params);
+  worker_thread()->Send(this, MSG_CREATECHANNEL, &msg);
+  return msg.data()->channel;
+}
+
+TransportChannelImpl* Transport::CreateChannel_w(
+    const std::string& name, const std::string& content_type) {
+  ASSERT(worker_thread()->IsCurrent());
+
+  TransportChannelImpl* impl = CreateTransportChannel(name, content_type);
+  impl->SignalReadableState.connect(this, &Transport::OnChannelReadableState);
+  impl->SignalWritableState.connect(this, &Transport::OnChannelWritableState);
+  impl->SignalRequestSignaling.connect(
+      this, &Transport::OnChannelRequestSignaling);
+  impl->SignalCandidateReady.connect(this, &Transport::OnChannelCandidateReady);
+
+  talk_base::CritScope cs(&crit_);
+  ASSERT(channels_.find(name) == channels_.end());
+  channels_[name] = impl;
+  destroyed_ = false;
+  if (connect_requested_) {
+    impl->Connect();
+    if (channels_.size() == 1) {
+      // If this is the first channel, then indicate that we have started
+      // connecting.
+      signaling_thread()->Post(this, MSG_CONNECTING, NULL);
+    }
+  }
+  return impl;
+}
+
+TransportChannelImpl* Transport::GetChannel(const std::string& name) {
+  talk_base::CritScope cs(&crit_);
+  ChannelMap::iterator iter = channels_.find(name);
+  return (iter != channels_.end()) ? iter->second : NULL;
+}
+
+bool Transport::HasChannels() {
+  talk_base::CritScope cs(&crit_);
+  return !channels_.empty();
+}
+
+void Transport::DestroyChannel(const std::string& name) {
+  ChannelParams params(name);
+  ChannelMessage msg(&params);
+  worker_thread()->Send(this, MSG_DESTROYCHANNEL, &msg);
+}
+
+void Transport::DestroyChannel_w(const std::string& name) {
+  ASSERT(worker_thread()->IsCurrent());
+
+  TransportChannelImpl* impl = NULL;
+  {
+    talk_base::CritScope cs(&crit_);
+    ChannelMap::iterator iter = channels_.find(name);
+    if (iter == channels_.end())
+      return;
+    impl = iter->second;
+    channels_.erase(iter);
+  }
+
+  if (connect_requested_ && channels_.empty()) {
+    // We're no longer attempting to connect.
+    signaling_thread()->Post(this, MSG_CONNECTING, NULL);
+  }
+
+  if (impl) {
+    // Check in case the deleted channel was the only non-writable channel.
+    OnChannelWritableState(impl);
+    DestroyTransportChannel(impl);
+  }
+}
+
+void Transport::ConnectChannels() {
+  ASSERT(signaling_thread()->IsCurrent());
+  worker_thread()->Send(this, MSG_CONNECTCHANNELS, NULL);
+}
+
+void Transport::ConnectChannels_w() {
+  ASSERT(worker_thread()->IsCurrent());
+  if (connect_requested_ || channels_.empty())
+    return;
+  connect_requested_ = true;
+  signaling_thread()->Post(
+      this, MSG_ONCHANNELCANDIDATEREADY, NULL);
+  CallChannels_w(&TransportChannelImpl::Connect);
+  if (!channels_.empty()) {
+    signaling_thread()->Post(this, MSG_CONNECTING, NULL);
+  }
+}
+
+void Transport::OnConnecting_s() {
+  ASSERT(signaling_thread()->IsCurrent());
+  SignalConnecting(this);
+}
+
+void Transport::DestroyAllChannels() {
+  ASSERT(signaling_thread()->IsCurrent());
+  worker_thread()->Send(this, MSG_DESTROYALLCHANNELS, NULL);
+  worker_thread()->Clear(this);
+  signaling_thread()->Clear(this);
+  destroyed_ = true;
+}
+
+void Transport::DestroyAllChannels_w() {
+  ASSERT(worker_thread()->IsCurrent());
+  std::vector<TransportChannelImpl*> impls;
+  {
+    talk_base::CritScope cs(&crit_);
+    for (ChannelMap::iterator iter = channels_.begin();
+         iter != channels_.end();
+         ++iter) {
+      impls.push_back(iter->second);
+    }
+    channels_.clear();
+  }
+
+  for (size_t i = 0; i < impls.size(); ++i)
+    DestroyTransportChannel(impls[i]);
+}
+
+void Transport::ResetChannels() {
+  ASSERT(signaling_thread()->IsCurrent());
+  worker_thread()->Send(this, MSG_RESETCHANNELS, NULL);
+}
+
+void Transport::ResetChannels_w() {
+  ASSERT(worker_thread()->IsCurrent());
+
+  // We are no longer attempting to connect
+  connect_requested_ = false;
+
+  // Clear out the old messages, they aren't relevant
+  talk_base::CritScope cs(&crit_);
+  ready_candidates_.clear();
+
+  // Reset all of the channels
+  CallChannels_w(&TransportChannelImpl::Reset);
+}
+
+void Transport::OnSignalingReady() {
+  ASSERT(signaling_thread()->IsCurrent());
+  if (destroyed_) return;
+
+  worker_thread()->Post(this, MSG_ONSIGNALINGREADY, NULL);
+
+  // Notify the subclass.
+  OnTransportSignalingReady();
+}
+
+void Transport::CallChannels_w(TransportChannelFunc func) {
+  ASSERT(worker_thread()->IsCurrent());
+  talk_base::CritScope cs(&crit_);
+  for (ChannelMap::iterator iter = channels_.begin();
+       iter != channels_.end();
+       ++iter) {
+    ((iter->second)->*func)();
+  }
+}
+
+bool Transport::VerifyCandidate(const Candidate& cand, ParseError* error) {
+  if (cand.address().IsLocalIP() && !allow_local_ips_)
+    return BadParse("candidate has local IP address", error);
+
+  // No address zero.
+  if (cand.address().IsAny()) {
+    return BadParse("candidate has address of zero", error);
+  }
+
+  // Disallow all ports below 1024, except for 80 and 443 on public addresses.
+  int port = cand.address().port();
+  if (port < 1024) {
+    if ((port != 80) && (port != 443))
+      return BadParse(
+          "candidate has port below 1024, but not 80 or 443", error);
+    if (cand.address().IsPrivateIP()) {
+      return BadParse(
+          "candidate has port of 80 or 443 with private IP address", error);
+    }
+  }
+
+  return true;
+}
+
+void Transport::OnRemoteCandidates(const std::vector<Candidate>& candidates) {
+  for (std::vector<Candidate>::const_iterator iter = candidates.begin();
+       iter != candidates.end();
+       ++iter) {
+    OnRemoteCandidate(*iter);
+  }
+}
+
+void Transport::OnRemoteCandidate(const Candidate& candidate) {
+  ASSERT(signaling_thread()->IsCurrent());
+  if (destroyed_) return;
+  if (!HasChannel(candidate.name())) {
+    LOG(LS_WARNING) << "Ignoring candidate for unknown channel "
+                    << candidate.name();
+    return;
+  }
+
+  // new candidate deleted when params is deleted
+  ChannelParams* params = new ChannelParams(new Candidate(candidate));
+  ChannelMessage* msg = new ChannelMessage(params);
+  worker_thread()->Post(this, MSG_ONREMOTECANDIDATE, msg);
+}
+
+void Transport::OnRemoteCandidate_w(const Candidate& candidate) {
+  ASSERT(worker_thread()->IsCurrent());
+  ChannelMap::iterator iter = channels_.find(candidate.name());
+  // It's ok for a channel to go away while this message is in transit.
+  if (iter != channels_.end()) {
+    iter->second->OnCandidate(candidate);
+  }
+}
+
+void Transport::OnChannelReadableState(TransportChannel* channel) {
+  ASSERT(worker_thread()->IsCurrent());
+  signaling_thread()->Post(this, MSG_READSTATE, NULL);
+}
+
+void Transport::OnChannelReadableState_s() {
+  ASSERT(signaling_thread()->IsCurrent());
+  bool readable = GetTransportState_s(true);
+  if (readable_ != readable) {
+    readable_ = readable;
+    SignalReadableState(this);
+  }
+}
+
+void Transport::OnChannelWritableState(TransportChannel* channel) {
+  ASSERT(worker_thread()->IsCurrent());
+  signaling_thread()->Post(this, MSG_WRITESTATE, NULL);
+}
+
+void Transport::OnChannelWritableState_s() {
+  ASSERT(signaling_thread()->IsCurrent());
+  bool writable = GetTransportState_s(false);
+  if (writable_ != writable) {
+    writable_ = writable;
+    SignalWritableState(this);
+  }
+}
+
+bool Transport::GetTransportState_s(bool read) {
+  ASSERT(signaling_thread()->IsCurrent());
+  bool result = false;
+  talk_base::CritScope cs(&crit_);
+  for (ChannelMap::iterator iter = channels_.begin();
+       iter != channels_.end();
+       ++iter) {
+    bool b = (read ? iter->second->readable() : iter->second->writable());
+    result = result || b;
+  }
+  return result;
+}
+
+void Transport::OnChannelRequestSignaling() {
+  ASSERT(worker_thread()->IsCurrent());
+  signaling_thread()->Post(this, MSG_REQUESTSIGNALING, NULL);
+}
+
+void Transport::OnChannelRequestSignaling_s() {
+  ASSERT(signaling_thread()->IsCurrent());
+  SignalRequestSignaling(this);
+}
+
+void Transport::OnChannelCandidateReady(TransportChannelImpl* channel,
+                                        const Candidate& candidate) {
+  ASSERT(worker_thread()->IsCurrent());
+  talk_base::CritScope cs(&crit_);
+  ready_candidates_.push_back(candidate);
+
+  // We hold any messages until the client lets us connect.
+  if (connect_requested_) {
+    signaling_thread()->Post(
+        this, MSG_ONCHANNELCANDIDATEREADY, NULL);
+  }
+}
+
+void Transport::OnChannelCandidateReady_s() {
+  ASSERT(signaling_thread()->IsCurrent());
+  ASSERT(connect_requested_);
+
+  std::vector<Candidate> candidates;
+  {
+    talk_base::CritScope cs(&crit_);
+    candidates.swap(ready_candidates_);
+  }
+
+  // we do the deleting of Candidate* here to keep the new above and
+  // delete below close to each other
+  if (!candidates.empty()) {
+    SignalCandidatesReady(this, candidates);
+  }
+}
+
+void Transport::OnMessage(talk_base::Message* msg) {
+  switch (msg->message_id) {
+  case MSG_CREATECHANNEL:
+    {
+      ChannelParams* params = static_cast<ChannelMessage*>(msg->pdata)->data();
+      params->channel = CreateChannel_w(params->name, params->content_type);
+    }
+    break;
+  case MSG_DESTROYCHANNEL:
+    {
+      ChannelParams* params = static_cast<ChannelMessage*>(msg->pdata)->data();
+      DestroyChannel_w(params->name);
+    }
+    break;
+  case MSG_CONNECTCHANNELS:
+    ConnectChannels_w();
+    break;
+  case MSG_RESETCHANNELS:
+    ResetChannels_w();
+    break;
+  case MSG_DESTROYALLCHANNELS:
+    DestroyAllChannels_w();
+    break;
+  case MSG_ONSIGNALINGREADY:
+    CallChannels_w(&TransportChannelImpl::OnSignalingReady);
+    break;
+  case MSG_ONREMOTECANDIDATE:
+    {
+      ChannelMessage* channel_msg = static_cast<ChannelMessage*>(msg->pdata);
+      ChannelParams* params = channel_msg->data();
+      OnRemoteCandidate_w(*(params->candidate));
+      delete params;
+      delete channel_msg;
+    }
+    break;
+  case MSG_CONNECTING:
+    OnConnecting_s();
+    break;
+  case MSG_READSTATE:
+    OnChannelReadableState_s();
+    break;
+  case MSG_WRITESTATE:
+    OnChannelWritableState_s();
+    break;
+  case MSG_REQUESTSIGNALING:
+    OnChannelRequestSignaling_s();
+    break;
+  case MSG_ONCHANNELCANDIDATEREADY:
+    OnChannelCandidateReady_s();
+    break;
+  }
+}
+
+bool TransportParser::ParseAddress(const buzz::XmlElement* elem,
+                                   const buzz::QName& address_name,
+                                   const buzz::QName& port_name,
+                                   talk_base::SocketAddress* address,
+                                   ParseError* error) {
+  if (!elem->HasAttr(address_name))
+    return BadParse("address does not have " + address_name.LocalPart(), error);
+  if (!elem->HasAttr(port_name))
+    return BadParse("address does not have " + port_name.LocalPart(), error);
+
+  address->SetIP(elem->Attr(address_name));
+  std::istringstream ist(elem->Attr(port_name));
+  int port = 0;
+  ist >> port;
+  address->SetPort(port);
+
+  return true;
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/transport.h b/talk/p2p/base/transport.h
new file mode 100644
index 0000000..d9a2d94
--- /dev/null
+++ b/talk/p2p/base/transport.h
@@ -0,0 +1,275 @@
+/*
+ * 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.
+ */
+
+// A Transport manages a set of named channels of the same type.
+//
+// Subclasses choose the appropriate class to instantiate for each channel;
+// however, this base class keeps track of the channels by name, watches their
+// state changes (in order to update the manager's state), and forwards
+// requests to begin connecting or to reset to each of the channels.
+//
+// On Threading:  Transport performs work on both the signaling and worker
+// threads.  For subclasses, the rule is that all signaling related calls will
+// be made on the signaling thread and all channel related calls (including
+// signaling for a channel) will be made on the worker thread.  When
+// information needs to be sent between the two threads, this class should do
+// the work (e.g., OnRemoteCandidate).
+//
+// Note: Subclasses must call DestroyChannels() in their own constructors.
+// It is not possible to do so here because the subclass constructor will
+// already have run.
+
+#ifndef TALK_P2P_BASE_TRANSPORT_H_
+#define TALK_P2P_BASE_TRANSPORT_H_
+
+#include <string>
+#include <map>
+#include <vector>
+#include "talk/base/criticalsection.h"
+#include "talk/base/messagequeue.h"
+#include "talk/base/sigslot.h"
+#include "talk/p2p/base/candidate.h"
+#include "talk/p2p/base/constants.h"
+
+namespace talk_base {
+class Thread;
+}
+
+namespace buzz {
+class QName;
+class XmlElement;
+}
+
+namespace cricket {
+
+struct ParseError;
+struct WriteError;
+class PortAllocator;
+class SessionManager;
+class Session;
+class TransportChannel;
+class TransportChannelImpl;
+
+typedef std::vector<buzz::XmlElement*> XmlElements;
+typedef std::vector<Candidate> Candidates;
+
+// Used to parse and serialize (write) transport candidates.  For
+// convenience of old code, Transports will implement TransportParser.
+// Parse/Write seems better than Serialize/Deserialize or
+// Create/Translate.
+class TransportParser {
+ public:
+  virtual bool ParseCandidates(SignalingProtocol protocol,
+                               const buzz::XmlElement* elem,
+                               Candidates* candidates,
+                               ParseError* error) = 0;
+  virtual bool WriteCandidates(SignalingProtocol protocol,
+                               const Candidates& candidates,
+                               XmlElements* candidate_elems,
+                               WriteError* error) = 0;
+
+  // Helper function to parse an element describing an address.  This
+  // retrieves the IP and port from the given element and verifies
+  // that they look like plausible values.
+  bool ParseAddress(const buzz::XmlElement* elem,
+                    const buzz::QName& address_name,
+                    const buzz::QName& port_name,
+                    talk_base::SocketAddress* address,
+                    ParseError* error);
+
+  virtual ~TransportParser() {}
+};
+
+class Transport : public talk_base::MessageHandler,
+                  public sigslot::has_slots<> {
+ public:
+  Transport(talk_base::Thread* signaling_thread,
+            talk_base::Thread* worker_thread,
+            const std::string& type,
+            PortAllocator* allocator);
+  virtual ~Transport();
+
+  // Returns the signaling thread. The app talks to Transport on this thread.
+  talk_base::Thread* signaling_thread() { return signaling_thread_; }
+  // Returns the worker thread. The actual networking is done on this thread.
+  talk_base::Thread* worker_thread() { return worker_thread_; }
+
+  // Returns the type of this transport.
+  const std::string& type() const { return type_; }
+
+  // Returns the port allocator object for this transport.
+  PortAllocator* port_allocator() { return allocator_; }
+
+  // Returns the readable and states of this manager.  These bits are the ORs
+  // of the corresponding bits on the managed channels.  Each time one of these
+  // states changes, a signal is raised.
+  bool readable() const { return readable_; }
+  bool writable() const { return writable_; }
+  sigslot::signal1<Transport*> SignalReadableState;
+  sigslot::signal1<Transport*> SignalWritableState;
+
+  // Returns whether the client has requested the channels to connect.
+  bool connect_requested() const { return connect_requested_; }
+
+  // Create, destroy, and lookup the channels of this type by their names.
+  TransportChannelImpl* CreateChannel(const std::string& name,
+                                      const std::string& content_type);
+  // Note: GetChannel may lead to race conditions, since the mutex is not held
+  // after the pointer is returned.
+  TransportChannelImpl* GetChannel(const std::string& name);
+  // Note: HasChannel does not lead to race conditions, unlike GetChannel.
+  bool HasChannel(const std::string& name) {
+    return (NULL != GetChannel(name));
+  }
+  bool HasChannels();
+  void DestroyChannel(const std::string& name);
+
+  // Tells all current and future channels to start connecting.  When the first
+  // channel begins connecting, the following signal is raised.
+  void ConnectChannels();
+  sigslot::signal1<Transport*> SignalConnecting;
+
+  // Resets all of the channels back to their initial state.  They are no
+  // longer connecting.
+  void ResetChannels();
+
+  // Destroys every channel created so far.
+  void DestroyAllChannels();
+
+  // Before any stanza is sent, the manager will request signaling.  Once
+  // signaling is available, the client should call OnSignalingReady.  Once
+  // this occurs, the transport (or its channels) can send any waiting stanzas.
+  // OnSignalingReady invokes OnTransportSignalingReady and then forwards this
+  // signal to each channel.
+  sigslot::signal1<Transport*> SignalRequestSignaling;
+  void OnSignalingReady();
+
+  // Handles sending of ready candidates and receiving of remote candidates.
+  sigslot::signal2<Transport*,
+                   const std::vector<Candidate>&> SignalCandidatesReady;
+  void OnRemoteCandidates(const std::vector<Candidate>& candidates);
+
+  // If candidate is not acceptable, returns false and sets error.
+  // Call this before calling OnRemoteCandidates.
+  virtual bool VerifyCandidate(const Candidate& candidate,
+                               ParseError* error);
+
+  // A transport message has generated an transport-specific error.  The
+  // stanza that caused the error is available in session_msg.  If false is
+  // returned, the error is considered unrecoverable, and the session is
+  // terminated.
+  // TODO: Make OnTransportError take an abstract data type
+  // rather than an XmlElement.  It isn't needed yet, but it might be
+  // later for Jingle compliance.
+  virtual void OnTransportError(const buzz::XmlElement* error) {}
+  sigslot::signal6<Transport*, const buzz::XmlElement*, const buzz::QName&,
+                   const std::string&, const std::string&,
+                   const buzz::XmlElement*>
+      SignalTransportError;
+
+  sigslot::signal2<Transport*, const std::string&> SignalChannelGone;
+
+  // (For testing purposes only.)  This indicates whether we will allow local
+  // IPs (e.g. 127.*) to be used as addresses for P2P.
+  bool allow_local_ips() const { return allow_local_ips_; }
+  void set_allow_local_ips(bool value) { allow_local_ips_ = value; }
+
+ protected:
+  // These are called by Create/DestroyChannel above in order to create or
+  // destroy the appropriate type of channel.
+  virtual TransportChannelImpl* CreateTransportChannel(
+      const std::string& name, const std::string &content_type) = 0;
+  virtual void DestroyTransportChannel(TransportChannelImpl* channel) = 0;
+
+  // Informs the subclass that we received the signaling ready message.
+  virtual void OnTransportSignalingReady() {}
+
+ private:
+  typedef std::map<std::string, TransportChannelImpl*> ChannelMap;
+
+  // Called when the state of a channel changes.
+  void OnChannelReadableState(TransportChannel* channel);
+  void OnChannelWritableState(TransportChannel* channel);
+
+  // Called when a channel requests signaling.
+  void OnChannelRequestSignaling();
+
+  // Called when a candidate is ready from remote peer.
+  void OnRemoteCandidate(const Candidate& candidate);
+  // Called when a candidate is ready from channel.
+  void OnChannelCandidateReady(TransportChannelImpl* channel,
+                               const Candidate& candidate);
+
+  // Dispatches messages to the appropriate handler (below).
+  void OnMessage(talk_base::Message* msg);
+
+  // These are versions of the above methods that are called only on a
+  // particular thread (s = signaling, w = worker).  The above methods post or
+  // send a message to invoke this version.
+  TransportChannelImpl* CreateChannel_w(const std::string& name,
+                                        const std::string& content_type);
+  void DestroyChannel_w(const std::string& name);
+  void ConnectChannels_w();
+  void ResetChannels_w();
+  void DestroyAllChannels_w();
+  void OnRemoteCandidate_w(const Candidate& candidate);
+  void OnChannelReadableState_s();
+  void OnChannelWritableState_s();
+  void OnChannelRequestSignaling_s();
+  void OnConnecting_s();
+
+  // Helper function that invokes the given function on every channel.
+  typedef void (TransportChannelImpl::* TransportChannelFunc)();
+  void CallChannels_w(TransportChannelFunc func);
+
+  // Computes the OR of the channel's read or write state (argument picks).
+  bool GetTransportState_s(bool read);
+
+  void OnChannelCandidateReady_s();
+
+  talk_base::Thread* signaling_thread_;
+  talk_base::Thread* worker_thread_;
+  std::string type_;
+  PortAllocator* allocator_;
+  bool destroyed_;
+  bool readable_;
+  bool writable_;
+  bool connect_requested_;
+  ChannelMap channels_;
+  // Buffers the ready_candidates so that SignalCanidatesReady can
+  // provide them in multiples.
+  std::vector<Candidate> ready_candidates_;
+  // Protects changes to channels and messages
+  talk_base::CriticalSection crit_;
+  bool allow_local_ips_;
+
+  DISALLOW_EVIL_CONSTRUCTORS(Transport);
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_TRANSPORT_H_
diff --git a/talk/p2p/base/transportchannel.cc b/talk/p2p/base/transportchannel.cc
new file mode 100644
index 0000000..ba076ac
--- /dev/null
+++ b/talk/p2p/base/transportchannel.cc
@@ -0,0 +1,56 @@
+/*
+ * 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 <sstream>
+#include "talk/p2p/base/transportchannel.h"
+
+namespace cricket {
+
+std::string TransportChannel::ToString() const {
+  const char READABLE_ABBREV[2] = { '_', 'R' };
+  const char WRITABLE_ABBREV[2] = { '_', 'W' };
+  std::stringstream ss;
+  ss << "Channel[" << name_ << "|" << READABLE_ABBREV[readable_]
+      << WRITABLE_ABBREV[writable_] << "]";
+  return ss.str();
+}
+
+void TransportChannel::set_readable(bool readable) {
+  if (readable_ != readable) {
+    readable_ = readable;
+    SignalReadableState(this);
+  }
+}
+
+void TransportChannel::set_writable(bool writable) {
+  if (writable_ != writable) {
+    writable_ = writable;
+    SignalWritableState(this);
+  }
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/transportchannel.h b/talk/p2p/base/transportchannel.h
new file mode 100644
index 0000000..ff252bf
--- /dev/null
+++ b/talk/p2p/base/transportchannel.h
@@ -0,0 +1,110 @@
+/*
+ * 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_P2P_BASE_TRANSPORTCHANNEL_H_
+#define TALK_P2P_BASE_TRANSPORTCHANNEL_H_
+
+#include <string>
+#include "talk/base/basictypes.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/socket.h"
+
+namespace cricket {
+
+class P2PTransportChannel;
+
+// A TransportChannel represents one logical stream of packets that are sent
+// between the two sides of a session.
+class TransportChannel: public sigslot::has_slots<> {
+ public:
+  TransportChannel(const std::string& name, const std::string &content_type)
+      : name_(name), content_type_(content_type),
+        readable_(false), writable_(false) {}
+  virtual ~TransportChannel() {}
+
+  // Returns the name of this channel.
+  const std::string& name() const { return name_; }
+  const std::string& content_type() const { return content_type_; }
+
+  // Returns the readable and states of this channel.  Each time one of these
+  // states changes, a signal is raised.  These states are aggregated by the
+  // TransportManager.
+  bool readable() const { return readable_; }
+  bool writable() const { return writable_; }
+  sigslot::signal1<TransportChannel*> SignalReadableState;
+  sigslot::signal1<TransportChannel*> SignalWritableState;
+
+  // Attempts to send the given packet.  The return value is < 0 on failure.
+  virtual int SendPacket(const char *data, size_t len) = 0;
+
+  // Sets a socket option on this channel.  Note that not all options are
+  // supported by all transport types.
+  virtual int SetOption(talk_base::Socket::Option opt, int value) = 0;
+
+  // Returns the most recent error that occurred on this channel.
+  virtual int GetError() = 0;
+
+  // This hack is here to allow the SocketMonitor to downcast to the
+  // P2PTransportChannel safely.
+  // TODO: Generalize network monitoring.
+  virtual P2PTransportChannel* GetP2PChannel() { return NULL; }
+
+  // Signalled each time a packet is received  on this channel.
+  sigslot::signal3<TransportChannel*, const char*, size_t> SignalReadPacket;
+
+  // This signal occurs when there is a change in the way that packets are
+  // being routed.  The address indicates the address of the first hop in the
+  // new route, if this is known.  If this cannot be determined or is not well-
+  // defined, then the channel may give an address of 0.
+  sigslot::signal2<TransportChannel*, const talk_base::SocketAddress&>
+      SignalRouteChange;
+
+  // Invoked when the channel is being destroyed.
+  sigslot::signal1<TransportChannel*> SignalDestroyed;
+
+  // Debugging description of this transport channel.
+  std::string ToString() const;
+
+ protected:
+  // Sets the readable state, signaling if necessary.
+  void set_readable(bool readable);
+
+  // Sets the writable state, signaling if necessary.
+  void set_writable(bool writable);
+
+ private:
+  std::string name_;
+  std::string content_type_;
+  bool readable_;
+  bool writable_;
+
+  DISALLOW_EVIL_CONSTRUCTORS(TransportChannel);
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_TRANSPORTCHANNEL_H_
diff --git a/talk/p2p/base/transportchannelimpl.h b/talk/p2p/base/transportchannelimpl.h
new file mode 100644
index 0000000..39e4288
--- /dev/null
+++ b/talk/p2p/base/transportchannelimpl.h
@@ -0,0 +1,81 @@
+/*
+ * 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_P2P_BASE_TRANSPORTCHANNELIMPL_H_
+#define TALK_P2P_BASE_TRANSPORTCHANNELIMPL_H_
+
+#include <string>
+#include "talk/p2p/base/transportchannel.h"
+
+namespace buzz { class XmlElement; }
+
+namespace cricket {
+
+class Transport;
+class Candidate;
+
+// Base class for real implementations of TransportChannel.  This includes some
+// methods called only by Transport, which do not need to be exposed to the
+// client.
+class TransportChannelImpl : public TransportChannel {
+ public:
+  TransportChannelImpl(const std::string& name, const std::string& content_type)
+    : TransportChannel(name, content_type) {}
+
+  // Returns the transport that created this channel.
+  virtual Transport* GetTransport() = 0;
+
+  // Begins the process of attempting to make a connection to the other client.
+  virtual void Connect() = 0;
+
+  // Resets this channel back to the initial state (i.e., not connecting).
+  virtual void Reset() = 0;
+
+  // Allows an individual channel to request signaling and be notified when it
+  // is ready.  This is useful if the individual named channels have need to
+  // send their own transport-info stanzas.
+  sigslot::signal0<> SignalRequestSignaling;
+  virtual void OnSignalingReady() = 0;
+
+  // Handles sending and receiving of candidates.  The Transport
+  // receives the candidates and may forward them to the relevant
+  // channel.
+  //
+  // Note: Since candidates are delivered asynchronously to the
+  // channel, they cannot return an error if the message is invalid.
+  // It is assumed that the Transport will have checked validity
+  // before forwarding.
+  sigslot::signal2<TransportChannelImpl*,
+                   const Candidate&> SignalCandidateReady;
+  virtual void OnCandidate(const Candidate& candidate) = 0;
+ private:
+  DISALLOW_EVIL_CONSTRUCTORS(TransportChannelImpl);
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_TRANSPORTCHANNELIMPL_H_
diff --git a/talk/p2p/base/transportchannelproxy.cc b/talk/p2p/base/transportchannelproxy.cc
new file mode 100644
index 0000000..1be9194
--- /dev/null
+++ b/talk/p2p/base/transportchannelproxy.cc
@@ -0,0 +1,107 @@
+/*
+ * 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/p2p/base/transportchannelproxy.h"
+#include "talk/base/common.h"
+#include "talk/p2p/base/transport.h"
+#include "talk/p2p/base/transportchannelimpl.h"
+
+namespace cricket {
+
+TransportChannelProxy::TransportChannelProxy(const std::string& name,
+                                             const std::string &content_type)
+    : TransportChannel(name, content_type), impl_(NULL) {
+}
+
+TransportChannelProxy::~TransportChannelProxy() {
+  if (impl_)
+    impl_->GetTransport()->DestroyChannel(impl_->name());
+}
+
+void TransportChannelProxy::SetImplementation(TransportChannelImpl* impl) {
+  impl_ = impl;
+  impl_->SignalReadableState.connect(
+      this, &TransportChannelProxy::OnReadableState);
+  impl_->SignalWritableState.connect(
+      this, &TransportChannelProxy::OnWritableState);
+  impl_->SignalReadPacket.connect(this, &TransportChannelProxy::OnReadPacket);
+  impl_->SignalRouteChange.connect(this, &TransportChannelProxy::OnRouteChange);
+  for (OptionList::iterator it = pending_options_.begin();
+       it != pending_options_.end();
+       ++it) {
+    impl_->SetOption(it->first, it->second);
+  }
+  pending_options_.clear();
+}
+
+int TransportChannelProxy::SendPacket(const char *data, size_t len) {
+  // Fail if we don't have an impl yet.
+  return (impl_) ? impl_->SendPacket(data, len) : -1;
+}
+
+int TransportChannelProxy::SetOption(talk_base::Socket::Option opt, int value) {
+  if (impl_)
+    return impl_->SetOption(opt, value);
+  pending_options_.push_back(OptionPair(opt, value));
+  return 0;
+}
+
+int TransportChannelProxy::GetError() {
+  ASSERT(impl_ != NULL);  // should not be used until channel is writable
+  return impl_->GetError();
+}
+
+P2PTransportChannel* TransportChannelProxy::GetP2PChannel() {
+  if (impl_) {
+      return impl_->GetP2PChannel();
+  }
+  return NULL;
+}
+
+void TransportChannelProxy::OnReadableState(TransportChannel* channel) {
+  ASSERT(channel == impl_);
+  set_readable(impl_->readable());
+}
+
+void TransportChannelProxy::OnWritableState(TransportChannel* channel) {
+  ASSERT(channel == impl_);
+  set_writable(impl_->writable());
+}
+
+void TransportChannelProxy::OnReadPacket(TransportChannel* channel,
+                                         const char* data, size_t size) {
+  ASSERT(channel == impl_);
+  SignalReadPacket(this, data, size);
+}
+
+void TransportChannelProxy::OnRouteChange(TransportChannel* channel,
+                                          const talk_base::SocketAddress& address) {
+  ASSERT(channel == impl_);
+  SignalRouteChange(this, address);
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/transportchannelproxy.h b/talk/p2p/base/transportchannelproxy.h
new file mode 100644
index 0000000..4601185
--- /dev/null
+++ b/talk/p2p/base/transportchannelproxy.h
@@ -0,0 +1,78 @@
+/*
+ * 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 _CRICKET_P2P_BASE_TRANSPORTCHANNELPROXY_H_
+#define _CRICKET_P2P_BASE_TRANSPORTCHANNELPROXY_H_
+
+#include <string>
+#include <vector>
+#include "talk/p2p/base/transportchannel.h"
+
+namespace cricket {
+
+class TransportChannelImpl;
+
+// Proxies calls between the client and the transport channel implementation.
+// This is needed because clients are allowed to create channels before the
+// network negotiation is complete.  Hence, we create a proxy up front, and
+// when negotiation completes, connect the proxy to the implementaiton.
+class TransportChannelProxy: public TransportChannel {
+ public:
+  TransportChannelProxy(const std::string& name, const std::string &content_type);
+  virtual ~TransportChannelProxy();
+
+  TransportChannelImpl* impl() const { return impl_; }
+
+  // Sets the implementation to which we will proxy.
+  void SetImplementation(TransportChannelImpl* impl);
+
+  // Implementation of the TransportChannel interface.  These simply forward to
+  // the implementation.
+  virtual int SendPacket(const char *data, size_t len);
+  virtual int SetOption(talk_base::Socket::Option opt, int value);
+  virtual int GetError();
+  virtual P2PTransportChannel* GetP2PChannel();
+
+ private:
+  typedef std::pair<talk_base::Socket::Option, int> OptionPair;
+  typedef std::vector<OptionPair> OptionList;
+  TransportChannelImpl* impl_;
+  OptionList pending_options_;
+
+  // Catch signals from the implementation channel.  These just forward to the
+  // client (after updating our state to match).
+  void OnReadableState(TransportChannel* channel);
+  void OnWritableState(TransportChannel* channel);
+  void OnReadPacket(TransportChannel* channel, const char* data, size_t size);
+  void OnRouteChange(TransportChannel* channel, const talk_base::SocketAddress& address);
+
+  DISALLOW_EVIL_CONSTRUCTORS(TransportChannelProxy);
+};
+
+}  // namespace cricket
+
+#endif  // _CRICKET_P2P_BASE_TRANSPORTCHANNELPROXY_H_
diff --git a/talk/p2p/base/udpport.cc b/talk/p2p/base/udpport.cc
new file mode 100644
index 0000000..28cf5d2
--- /dev/null
+++ b/talk/p2p/base/udpport.cc
@@ -0,0 +1,117 @@
+/*
+ * 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/p2p/base/udpport.h"
+
+#include "talk/base/asyncpacketsocket.h"
+#include "talk/base/logging.h"
+#include "talk/p2p/base/common.h"
+
+namespace cricket {
+
+const std::string LOCAL_PORT_TYPE("local");
+
+UDPPort::UDPPort(talk_base::Thread* thread,
+                 talk_base::PacketSocketFactory* factory,
+                 talk_base::Network* network,
+                 uint32 ip, int min_port, int max_port)
+    : Port(thread, LOCAL_PORT_TYPE, factory, network, ip, min_port, max_port),
+      socket_(NULL),
+      error_(0) {
+}
+
+bool UDPPort::Init() {
+  socket_ =  factory_->CreateUdpSocket(
+      talk_base::SocketAddress(ip_, 0), min_port_, max_port_);
+  if (!socket_) {
+    LOG_J(LS_WARNING, this) << "UDP socket creation failed";
+    return false;
+  }
+  socket_->SignalReadPacket.connect(this, &UDPPort::OnReadPacket);
+  return true;
+}
+
+UDPPort::~UDPPort() {
+  delete socket_;
+}
+
+void UDPPort::PrepareAddress() {
+  bool allocated;
+  talk_base::SocketAddress address = socket_->GetLocalAddress(&allocated);
+  if (allocated) {
+    AddAddress(address, "udp", true);
+  } else {
+    socket_->SignalAddressReady.connect(this, &UDPPort::OnAddresReady);
+  }
+}
+
+Connection* UDPPort::CreateConnection(const Candidate& address,
+                                      CandidateOrigin origin) {
+  if (address.protocol() != "udp")
+    return NULL;
+
+  Connection* conn = new ProxyConnection(this, 0, address);
+  AddConnection(conn);
+  return conn;
+}
+
+int UDPPort::SendTo(const void* data, size_t size,
+                    const talk_base::SocketAddress& addr, bool payload) {
+  int sent = socket_->SendTo(data, size, addr);
+  if (sent < 0) {
+    error_ = socket_->GetError();
+    LOG_J(LS_ERROR, this) << "UDP send of " << size
+                          << " bytes failed with error " << error_;
+  }
+  return sent;
+}
+
+int UDPPort::SetOption(talk_base::Socket::Option opt, int value) {
+  return socket_->SetOption(opt, value);
+}
+
+int UDPPort::GetError() {
+  return error_;
+}
+
+void UDPPort::OnAddresReady(talk_base::AsyncPacketSocket* socket,
+                            const talk_base::SocketAddress& address) {
+  AddAddress(address, "udp", true);
+}
+
+void UDPPort::OnReadPacket(
+    talk_base::AsyncPacketSocket* socket, const char* data, size_t size,
+    const talk_base::SocketAddress& remote_addr) {
+  ASSERT(socket == socket_);
+  if (Connection* conn = GetConnection(remote_addr)) {
+    conn->OnReadPacket(data, size);
+  } else {
+    Port::OnReadPacket(data, size, remote_addr);
+  }
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/udpport.h b/talk/p2p/base/udpport.h
new file mode 100644
index 0000000..f7588a6
--- /dev/null
+++ b/talk/p2p/base/udpport.h
@@ -0,0 +1,93 @@
+/*
+ * 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_P2P_BASE_UDPPORT_H_
+#define TALK_P2P_BASE_UDPPORT_H_
+
+#include <string>
+
+#include "talk/p2p/base/port.h"
+
+namespace talk_base {
+class Thread;
+class Network;
+class SocketAddress;
+}
+
+namespace cricket {
+
+extern const std::string LOCAL_PORT_TYPE;  // type of UDP ports
+
+// Communicates using a local UDP port.
+class UDPPort : public Port {
+ public:
+  static UDPPort* Create(talk_base::Thread* thread,
+                         talk_base::PacketSocketFactory* factory,
+                         talk_base::Network* network,
+                         uint32 ip, int min_port, int max_port) {
+    UDPPort* port = new UDPPort(thread, factory, network,
+                                ip, min_port, max_port);
+    if (!port->Init()) {
+      delete port;
+      port = NULL;
+    }
+    return port;
+  }
+  virtual ~UDPPort();
+
+  virtual void PrepareAddress();
+  virtual Connection* CreateConnection(const Candidate& address,
+                                       CandidateOrigin origin);
+
+  virtual int SetOption(talk_base::Socket::Option opt, int value);
+  virtual int GetError();
+
+ protected:
+  UDPPort(talk_base::Thread* thread, talk_base::PacketSocketFactory* factory,
+          talk_base::Network* network, uint32 ip, int min_port, int max_port);
+  bool Init();
+
+  // Handles sending using the local UDP socket.
+  virtual int SendTo(const void* data, size_t size,
+                     const talk_base::SocketAddress& remote_addr, bool payload);
+
+  void OnAddresReady(talk_base::AsyncPacketSocket* socket,
+                     const talk_base::SocketAddress& address);
+
+  // Dispatches the given packet to the port or connection as appropriate.
+  void OnReadPacket(talk_base::AsyncPacketSocket* socket,
+                    const char* data, size_t size,
+                    const talk_base::SocketAddress& remote_addr);
+
+ private:
+  talk_base::AsyncPacketSocket* socket_;
+  int error_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_UDPPORT_H_
diff --git a/talk/p2p/client/basicportallocator.cc b/talk/p2p/client/basicportallocator.cc
new file mode 100644
index 0000000..c023e9e
--- /dev/null
+++ b/talk/p2p/client/basicportallocator.cc
@@ -0,0 +1,802 @@
+/*
+ * 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 <string>
+#include <vector>
+
+#include "talk/base/common.h"
+#include "talk/base/helpers.h"
+#include "talk/base/host.h"
+#include "talk/base/logging.h"
+#include "talk/p2p/client/basicportallocator.h"
+#include "talk/p2p/base/common.h"
+#include "talk/p2p/base/port.h"
+#include "talk/p2p/base/relayport.h"
+#include "talk/p2p/base/stunport.h"
+#include "talk/p2p/base/tcpport.h"
+#include "talk/p2p/base/udpport.h"
+
+using talk_base::CreateRandomId;
+using talk_base::CreateRandomString;
+
+namespace {
+
+const uint32 MSG_CONFIG_START = 1;
+const uint32 MSG_CONFIG_READY = 2;
+const uint32 MSG_ALLOCATE = 3;
+const uint32 MSG_ALLOCATION_PHASE = 4;
+const uint32 MSG_SHAKE = 5;
+
+const uint32 ALLOCATE_DELAY = 250;
+const uint32 ALLOCATION_STEP_DELAY = 1 * 1000;
+
+const int PHASE_UDP = 0;
+const int PHASE_RELAY = 1;
+const int PHASE_TCP = 2;
+const int PHASE_SSLTCP = 3;
+const int kNumPhases = 4;
+
+const float PREF_LOCAL_UDP = 1.0f;
+const float PREF_LOCAL_STUN = 0.9f;
+const float PREF_LOCAL_TCP = 0.8f;
+const float PREF_RELAY = 0.5f;
+
+// Modifiers of the above constants
+const float RELAY_PRIMARY_PREF_MODIFIER = 0.0f;
+const float RELAY_BACKUP_PREF_MODIFIER = -0.2f;
+
+// Returns the phase in which a given local candidate (or rather, the port that
+// gave rise to that local candidate) would have been created.
+int LocalCandidateToPhase(const cricket::Candidate& candidate) {
+  cricket::ProtocolType proto;
+  bool result = cricket::StringToProto(candidate.protocol().c_str(), &proto);
+  if (result) {
+    if (candidate.type() == cricket::LOCAL_PORT_TYPE) {
+      switch (proto) {
+      case cricket::PROTO_UDP: return PHASE_UDP;
+      case cricket::PROTO_TCP: return PHASE_TCP;
+      default: ASSERT(false);
+      }
+    } else if (candidate.type() == cricket::STUN_PORT_TYPE) {
+      return PHASE_UDP;
+    } else if (candidate.type() == cricket::RELAY_PORT_TYPE) {
+      switch (proto) {
+      case cricket::PROTO_UDP: return PHASE_RELAY;
+      case cricket::PROTO_TCP: return PHASE_TCP;
+      case cricket::PROTO_SSLTCP: return PHASE_SSLTCP;
+      default: ASSERT(false);
+      }
+    } else {
+      ASSERT(false);
+    }
+  } else {
+    ASSERT(false);
+  }
+  return PHASE_UDP;  // reached only with assert failure
+}
+
+const int SHAKE_MIN_DELAY = 45 * 1000;  // 45 seconds
+const int SHAKE_MAX_DELAY = 90 * 1000;  // 90 seconds
+
+int ShakeDelay() {
+  int range = SHAKE_MAX_DELAY - SHAKE_MIN_DELAY + 1;
+  return SHAKE_MIN_DELAY + CreateRandomId() % range;
+}
+
+}  // namespace
+
+namespace cricket {
+
+const uint32 DISABLE_ALL_PHASES =
+  PORTALLOCATOR_DISABLE_UDP
+  | PORTALLOCATOR_DISABLE_TCP
+  | PORTALLOCATOR_DISABLE_STUN
+  | PORTALLOCATOR_DISABLE_RELAY;
+
+// Performs the allocation of ports, in a sequenced (timed) manner, for a given
+// network and IP address.
+class AllocationSequence : public talk_base::MessageHandler {
+ public:
+  AllocationSequence(BasicPortAllocatorSession* session,
+                     talk_base::Network* network,
+                     PortConfiguration* config,
+                     uint32 flags);
+  ~AllocationSequence();
+
+  // Disables the phases for a new sequence that this one already covers for an
+  // equivalent network setup.
+  void DisableEquivalentPhases(talk_base::Network* network,
+      PortConfiguration* config, uint32* flags);
+
+  // Starts and stops the sequence.  When started, it will continue allocating
+  // new ports on its own timed schedule.
+  void Start();
+  void Stop();
+
+  // MessageHandler
+  void OnMessage(talk_base::Message* msg);
+
+  void EnableProtocol(ProtocolType proto);
+  bool ProtocolEnabled(ProtocolType proto) const;
+
+ private:
+  typedef std::vector<ProtocolType> ProtocolList;
+
+  void CreateUDPPorts();
+  void CreateTCPPorts();
+  void CreateStunPorts();
+  void CreateRelayPorts();
+
+  BasicPortAllocatorSession* session_;
+  talk_base::Network* network_;
+  uint32 ip_;
+  PortConfiguration* config_;
+  bool running_;
+  int step_;
+  int step_of_phase_[kNumPhases];
+  uint32 flags_;
+  ProtocolList protocols_;
+};
+
+
+// BasicPortAllocator
+
+BasicPortAllocator::BasicPortAllocator(
+    talk_base::NetworkManager* network_manager,
+    talk_base::PacketSocketFactory* socket_factory)
+    : network_manager_(network_manager),
+      socket_factory_(socket_factory),
+      best_writable_phase_(-1),
+      allow_tcp_listen_(true) {
+  ASSERT(socket_factory_ != NULL);
+}
+
+BasicPortAllocator::BasicPortAllocator(
+    talk_base::NetworkManager* network_manager,
+    talk_base::PacketSocketFactory* socket_factory,
+    const talk_base::SocketAddress& stun_address,
+    const talk_base::SocketAddress& relay_address_udp,
+    const talk_base::SocketAddress& relay_address_tcp,
+    const talk_base::SocketAddress& relay_address_ssl)
+    : network_manager_(network_manager),
+      socket_factory_(socket_factory),
+      stun_address_(stun_address),
+      relay_address_udp_(relay_address_udp),
+      relay_address_tcp_(relay_address_tcp),
+      relay_address_ssl_(relay_address_ssl),
+      best_writable_phase_(-1),
+      allow_tcp_listen_(true) {
+}
+
+BasicPortAllocator::~BasicPortAllocator() {
+}
+
+int BasicPortAllocator::best_writable_phase() const {
+  // If we are configured with an HTTP proxy, the best bet is to use the relay
+  if ((best_writable_phase_ == -1)
+      && ((proxy().type == talk_base::PROXY_HTTPS)
+          || (proxy().type == talk_base::PROXY_UNKNOWN))) {
+    return PHASE_RELAY;
+  }
+  return best_writable_phase_;
+}
+
+PortAllocatorSession *BasicPortAllocator::CreateSession(
+    const std::string &name, const std::string &session_type) {
+  return new BasicPortAllocatorSession(this, name, session_type);
+}
+
+void BasicPortAllocator::AddWritablePhase(int phase) {
+  if ((best_writable_phase_ == -1) || (phase < best_writable_phase_))
+    best_writable_phase_ = phase;
+}
+
+// BasicPortAllocatorSession
+BasicPortAllocatorSession::BasicPortAllocatorSession(
+    BasicPortAllocator *allocator,
+    const std::string &name,
+    const std::string &session_type)
+    : PortAllocatorSession(allocator->flags()), allocator_(allocator),
+      name_(name), session_type_(session_type), network_thread_(NULL),
+      allocation_started_(false), running_(false) {
+}
+
+BasicPortAllocatorSession::~BasicPortAllocatorSession() {
+  if (network_thread_ != NULL)
+    network_thread_->Clear(this);
+
+  std::vector<PortData>::iterator it;
+  for (it = ports_.begin(); it != ports_.end(); it++)
+    delete it->port;
+
+  for (uint32 i = 0; i < configs_.size(); ++i)
+    delete configs_[i];
+
+  for (uint32 i = 0; i < sequences_.size(); ++i)
+    delete sequences_[i];
+}
+
+void BasicPortAllocatorSession::GetInitialPorts() {
+  network_thread_ = talk_base::Thread::Current();
+
+  network_thread_->Post(this, MSG_CONFIG_START);
+
+  if (flags() & PORTALLOCATOR_ENABLE_SHAKER)
+    network_thread_->PostDelayed(ShakeDelay(), this, MSG_SHAKE);
+}
+
+void BasicPortAllocatorSession::StartGetAllPorts() {
+  ASSERT(talk_base::Thread::Current() == network_thread_);
+  running_ = true;
+  if (allocation_started_)
+    network_thread_->PostDelayed(ALLOCATE_DELAY, this, MSG_ALLOCATE);
+  for (uint32 i = 0; i < sequences_.size(); ++i)
+    sequences_[i]->Start();
+  for (size_t i = 0; i < ports_.size(); ++i)
+    ports_[i].port->Start();
+}
+
+void BasicPortAllocatorSession::StopGetAllPorts() {
+  ASSERT(talk_base::Thread::Current() == network_thread_);
+  running_ = false;
+  network_thread_->Clear(this, MSG_ALLOCATE);
+  for (uint32 i = 0; i < sequences_.size(); ++i)
+    sequences_[i]->Stop();
+}
+
+void BasicPortAllocatorSession::OnMessage(talk_base::Message *message) {
+  switch (message->message_id) {
+  case MSG_CONFIG_START:
+    ASSERT(talk_base::Thread::Current() == network_thread_);
+    GetPortConfigurations();
+    break;
+
+  case MSG_CONFIG_READY:
+    ASSERT(talk_base::Thread::Current() == network_thread_);
+    OnConfigReady(static_cast<PortConfiguration*>(message->pdata));
+    break;
+
+  case MSG_ALLOCATE:
+    ASSERT(talk_base::Thread::Current() == network_thread_);
+    OnAllocate();
+    break;
+
+  case MSG_SHAKE:
+    ASSERT(talk_base::Thread::Current() == network_thread_);
+    OnShake();
+    break;
+
+  default:
+    ASSERT(false);
+  }
+}
+
+void BasicPortAllocatorSession::GetPortConfigurations() {
+  PortConfiguration* config = new PortConfiguration(allocator_->stun_address(),
+                                                    CreateRandomString(16),
+                                                    CreateRandomString(16),
+                                                    "");
+  PortConfiguration::PortList ports;
+  if (!allocator_->relay_address_udp().IsAny())
+    ports.push_back(ProtocolAddress(
+        allocator_->relay_address_udp(), PROTO_UDP));
+  if (!allocator_->relay_address_tcp().IsAny())
+    ports.push_back(ProtocolAddress(
+        allocator_->relay_address_tcp(), PROTO_TCP));
+  if (!allocator_->relay_address_ssl().IsAny())
+    ports.push_back(ProtocolAddress(
+        allocator_->relay_address_ssl(), PROTO_SSLTCP));
+  config->AddRelay(ports, RELAY_PRIMARY_PREF_MODIFIER);
+
+  ConfigReady(config);
+}
+
+void BasicPortAllocatorSession::ConfigReady(PortConfiguration* config) {
+  network_thread_->Post(this, MSG_CONFIG_READY, config);
+}
+
+// Adds a configuration to the list.
+void BasicPortAllocatorSession::OnConfigReady(PortConfiguration* config) {
+  if (config)
+    configs_.push_back(config);
+
+  AllocatePorts();
+}
+
+void BasicPortAllocatorSession::AllocatePorts() {
+  ASSERT(talk_base::Thread::Current() == network_thread_);
+  network_thread_->Post(this, MSG_ALLOCATE);
+}
+
+// For each network, see if we have a sequence that covers it already.  If not,
+// create a new sequence to create the appropriate ports.
+void BasicPortAllocatorSession::OnAllocate() {
+  std::vector<talk_base::Network*> networks;
+
+  if (!allocator_->network_manager()->GetNetworks(&networks)) {
+    LOG(LS_ERROR) << "Failed to enumerate networks";
+  } else if (networks.empty()) {
+    LOG(LS_WARNING) << "Machine has no networks; no ports will be allocated";
+  } else {
+    for (uint32 i = 0; i < networks.size(); ++i) {
+      PortConfiguration* config = NULL;
+      if (configs_.size() > 0)
+        config = configs_.back();
+
+      uint32 sequence_flags = flags();
+
+      // Disables phases that are not specified in this config.
+      if (!config || config->stun_address.IsNil()) {
+        // No STUN ports specified in this config.
+        sequence_flags |= PORTALLOCATOR_DISABLE_STUN;
+      }
+      if (!config || config->relays.empty()) {
+        // No relay ports specified in this config.
+        sequence_flags |= PORTALLOCATOR_DISABLE_RELAY;
+      }
+
+      // Disable phases that would only create ports equivalent to ones that we
+      // have already made.
+      DisableEquivalentPhases(networks[i], config, &sequence_flags);
+
+      if ((sequence_flags & DISABLE_ALL_PHASES) == DISABLE_ALL_PHASES) {
+        // New AllocationSequence would have nothing to do, so don't make it.
+        continue;
+      }
+
+      AllocationSequence* sequence =
+          new AllocationSequence(this, networks[i], config, sequence_flags);
+      if (running_)
+        sequence->Start();
+
+      sequences_.push_back(sequence);
+    }
+  }
+
+  allocation_started_ = true;
+  if (running_)
+    network_thread_->PostDelayed(ALLOCATE_DELAY, this, MSG_ALLOCATE);
+}
+
+void BasicPortAllocatorSession::DisableEquivalentPhases(
+    talk_base::Network* network, PortConfiguration* config, uint32* flags) {
+  for (uint32 i = 0; i < sequences_.size() &&
+      (*flags & DISABLE_ALL_PHASES) != DISABLE_ALL_PHASES; ++i) {
+    sequences_[i]->DisableEquivalentPhases(network, config, flags);
+  }
+}
+
+void BasicPortAllocatorSession::AddAllocatedPort(Port* port,
+                                                 AllocationSequence * seq,
+                                                 float pref,
+                                                 bool prepare_address) {
+  if (!port)
+    return;
+
+  port->set_name(name_);
+  port->set_preference(pref);
+  port->set_generation(generation());
+  if (allocator_->proxy().type != talk_base::PROXY_NONE)
+    port->set_proxy(allocator_->user_agent(), allocator_->proxy());
+
+  PortData data;
+  data.port = port;
+  data.sequence = seq;
+  data.ready = false;
+  ports_.push_back(data);
+
+  port->SignalAddressReady.connect(this,
+      &BasicPortAllocatorSession::OnAddressReady);
+  port->SignalConnectionCreated.connect(this,
+      &BasicPortAllocatorSession::OnConnectionCreated);
+  port->SignalDestroyed.connect(this,
+      &BasicPortAllocatorSession::OnPortDestroyed);
+  LOG_J(LS_INFO, port) << "Added port to allocator";
+
+  if (prepare_address)
+    port->PrepareAddress();
+  if (running_)
+    port->Start();
+}
+
+void BasicPortAllocatorSession::OnAddressReady(Port *port) {
+  ASSERT(talk_base::Thread::Current() == network_thread_);
+  std::vector<PortData>::iterator it
+    = std::find(ports_.begin(), ports_.end(), port);
+  ASSERT(it != ports_.end());
+  if (it->ready)
+    return;
+  it->ready = true;
+  SignalPortReady(this, port);
+
+  // Only accumulate the candidates whose protocol has been enabled
+  std::vector<Candidate> candidates;
+  const std::vector<Candidate>& potentials = port->candidates();
+  for (size_t i = 0; i < potentials.size(); ++i) {
+    ProtocolType pvalue;
+    if (!StringToProto(potentials[i].protocol().c_str(), &pvalue))
+      continue;
+    if (it->sequence->ProtocolEnabled(pvalue)) {
+      candidates.push_back(potentials[i]);
+    }
+  }
+  if (!candidates.empty()) {
+    SignalCandidatesReady(this, candidates);
+  }
+}
+
+void BasicPortAllocatorSession::OnProtocolEnabled(AllocationSequence * seq,
+                                                  ProtocolType proto) {
+  std::vector<Candidate> candidates;
+  for (std::vector<PortData>::iterator it = ports_.begin();
+       it != ports_.end(); ++it) {
+    if (!it->ready || (it->sequence != seq))
+      continue;
+
+    const std::vector<Candidate>& potentials = it->port->candidates();
+    for (size_t i = 0; i < potentials.size(); ++i) {
+      ProtocolType pvalue;
+      if (!StringToProto(potentials[i].protocol().c_str(), &pvalue))
+        continue;
+      if (pvalue == proto) {
+        candidates.push_back(potentials[i]);
+      }
+    }
+  }
+  if (!candidates.empty()) {
+    SignalCandidatesReady(this, candidates);
+  }
+}
+
+void BasicPortAllocatorSession::OnPortDestroyed(Port* port) {
+  ASSERT(talk_base::Thread::Current() == network_thread_);
+  std::vector<PortData>::iterator iter =
+      std::find(ports_.begin(), ports_.end(), port);
+  ASSERT(iter != ports_.end());
+  ports_.erase(iter);
+
+  LOG_J(LS_INFO, port) << "Removed port from allocator ("
+                       << static_cast<int>(ports_.size()) << " remaining)";
+}
+
+void BasicPortAllocatorSession::OnConnectionCreated(Port* port,
+                                                    Connection* conn) {
+  conn->SignalStateChange.connect(this,
+    &BasicPortAllocatorSession::OnConnectionStateChange);
+}
+
+void BasicPortAllocatorSession::OnConnectionStateChange(Connection* conn) {
+  if (conn->write_state() == Connection::STATE_WRITABLE)
+    allocator_->AddWritablePhase(
+      LocalCandidateToPhase(conn->local_candidate()));
+}
+
+void BasicPortAllocatorSession::OnShake() {
+  LOG(INFO) << ">>>>> SHAKE <<<<< >>>>> SHAKE <<<<< >>>>> SHAKE <<<<<";
+
+  std::vector<Port*> ports;
+  std::vector<Connection*> connections;
+
+  for (size_t i = 0; i < ports_.size(); ++i) {
+    if (ports_[i].ready)
+      ports.push_back(ports_[i].port);
+  }
+
+  for (size_t i = 0; i < ports.size(); ++i) {
+    Port::AddressMap::const_iterator iter;
+    for (iter = ports[i]->connections().begin();
+         iter != ports[i]->connections().end();
+         ++iter) {
+      connections.push_back(iter->second);
+    }
+  }
+
+  LOG(INFO) << ">>>>> Destroying " << ports.size() << " ports and "
+            << connections.size() << " connections";
+
+  for (size_t i = 0; i < connections.size(); ++i)
+    connections[i]->Destroy();
+
+  if (running_ || (ports.size() > 0) || (connections.size() > 0))
+    network_thread_->PostDelayed(ShakeDelay(), this, MSG_SHAKE);
+}
+
+// AllocationSequence
+
+AllocationSequence::AllocationSequence(BasicPortAllocatorSession* session,
+                                       talk_base::Network* network,
+                                       PortConfiguration* config,
+                                       uint32 flags)
+  : session_(session), network_(network), ip_(network->ip()), config_(config),
+    running_(false), step_(0), flags_(flags) {
+  // All of the phases up until the best-writable phase so far run in step 0.
+  // The other phases follow sequentially in the steps after that.  If there is
+  // no best-writable so far, then only phase 0 occurs in step 0.
+  int last_phase_in_step_zero =
+      talk_base::_max(0, session->allocator()->best_writable_phase());
+  for (int phase = 0; phase < kNumPhases; ++phase)
+    step_of_phase_[phase] = talk_base::_max(0, phase - last_phase_in_step_zero);
+
+  // Immediately perform phase 0.
+  OnMessage(NULL);
+}
+
+AllocationSequence::~AllocationSequence() {
+  session_->network_thread()->Clear(this);
+}
+
+void AllocationSequence::DisableEquivalentPhases(talk_base::Network* network,
+    PortConfiguration* config, uint32* flags) {
+  if (!((network == network_) && (ip_ == network->ip()))) {
+    // Different network setup; nothing is equivalent.
+    return;
+  }
+
+  // Else turn off the stuff that we've already got covered.
+
+  // Every config implicitly specifies local, so turn that off right away.
+  *flags |= PORTALLOCATOR_DISABLE_UDP;
+  *flags |= PORTALLOCATOR_DISABLE_TCP;
+
+  if (config_ && config) {
+    if (config_->stun_address == config->stun_address) {
+      // Already got this STUN server covered.
+      *flags |= PORTALLOCATOR_DISABLE_STUN;
+    }
+    if (!config_->relays.empty()) {
+      // Already got relays covered.
+      // NOTE: This will even skip a _different_ set of relay servers if we
+      // were to be given one, but that never happens in our codebase. Should
+      // probably get rid of the list in PortConfiguration and just keep a
+      // single relay server in each one.
+      *flags |= PORTALLOCATOR_DISABLE_RELAY;
+    }
+  }
+}
+
+void AllocationSequence::Start() {
+  running_ = true;
+  session_->network_thread()->PostDelayed(ALLOCATION_STEP_DELAY,
+                                          this,
+                                          MSG_ALLOCATION_PHASE);
+}
+
+void AllocationSequence::Stop() {
+  running_ = false;
+  session_->network_thread()->Clear(this, MSG_ALLOCATION_PHASE);
+}
+
+void AllocationSequence::OnMessage(talk_base::Message* msg) {
+  ASSERT(talk_base::Thread::Current() == session_->network_thread());
+  if (msg)
+    ASSERT(msg->message_id == MSG_ALLOCATION_PHASE);
+
+  const char* const PHASE_NAMES[kNumPhases] = {
+    "Udp", "Relay", "Tcp", "SslTcp"
+  };
+
+  // Perform all of the phases in the current step.
+  for (int phase = 0; phase < kNumPhases; phase++) {
+    if (step_of_phase_[phase] != step_)
+      continue;
+
+    LOG_J(LS_INFO, network_) << "Allocation Phase=" << PHASE_NAMES[phase]
+                             << " (Step=" << step_ << ")";
+
+    switch (phase) {
+    case PHASE_UDP:
+      CreateUDPPorts();
+      CreateStunPorts();
+      EnableProtocol(PROTO_UDP);
+      break;
+
+    case PHASE_RELAY:
+      CreateRelayPorts();
+      break;
+
+    case PHASE_TCP:
+      CreateTCPPorts();
+      EnableProtocol(PROTO_TCP);
+      break;
+
+    case PHASE_SSLTCP:
+      EnableProtocol(PROTO_SSLTCP);
+      break;
+
+    default:
+      ASSERT(false);
+    }
+  }
+
+  // TODO: use different delays for each stage
+  step_ += 1;
+  if (running_) {
+    session_->network_thread()->PostDelayed(ALLOCATION_STEP_DELAY,
+                                            this,
+                                            MSG_ALLOCATION_PHASE);
+  }
+}
+
+void AllocationSequence::EnableProtocol(ProtocolType proto) {
+  if (!ProtocolEnabled(proto)) {
+    protocols_.push_back(proto);
+    session_->OnProtocolEnabled(this, proto);
+  }
+}
+
+bool AllocationSequence::ProtocolEnabled(ProtocolType proto) const {
+  for (ProtocolList::const_iterator it = protocols_.begin();
+       it != protocols_.end(); ++it) {
+    if (*it == proto)
+      return true;
+  }
+  return false;
+}
+
+void AllocationSequence::CreateUDPPorts() {
+  if (flags_ & PORTALLOCATOR_DISABLE_UDP) {
+    LOG(LS_VERBOSE) << "AllocationSequence: UDP ports disabled, skipping.";
+    return;
+  }
+
+  Port* port = UDPPort::Create(session_->network_thread(),
+                               session_->allocator()->socket_factory(),
+                               network_, ip_,
+                               session_->allocator()->min_port(),
+                               session_->allocator()->max_port());
+  if (port)
+    session_->AddAllocatedPort(port, this, PREF_LOCAL_UDP);
+}
+
+void AllocationSequence::CreateTCPPorts() {
+  if (flags_ & PORTALLOCATOR_DISABLE_TCP) {
+    LOG(LS_VERBOSE) << "AllocationSequence: TCP ports disabled, skipping.";
+    return;
+  }
+
+  Port* port = TCPPort::Create(session_->network_thread(),
+                               session_->allocator()->socket_factory(),
+                               network_, ip_,
+                               session_->allocator()->min_port(),
+                               session_->allocator()->max_port(),
+                               session_->allocator()->allow_tcp_listen());
+  if (port)
+    session_->AddAllocatedPort(port, this, PREF_LOCAL_TCP);
+}
+
+void AllocationSequence::CreateStunPorts() {
+  if (flags_ & PORTALLOCATOR_DISABLE_STUN) {
+    LOG(LS_VERBOSE) << "AllocationSequence: STUN ports disabled, skipping.";
+    return;
+  }
+
+  // If BasicPortAllocatorSession::OnAllocate left STUN ports enabled then we
+  // ought to have an address for them here.
+  ASSERT(config_ && !config_->stun_address.IsNil());
+  if (!(config_ && !config_->stun_address.IsNil())) {
+    LOG(LS_WARNING)
+        << "AllocationSequence: No STUN server configured, skipping.";
+    return;
+  }
+
+  Port* port = StunPort::Create(session_->network_thread(),
+                                session_->allocator()->socket_factory(),
+                                network_, ip_,
+                                session_->allocator()->min_port(),
+                                session_->allocator()->max_port(),
+                                config_->stun_address);
+  if (port)
+    session_->AddAllocatedPort(port, this, PREF_LOCAL_STUN);
+}
+
+void AllocationSequence::CreateRelayPorts() {
+  if (flags_ & PORTALLOCATOR_DISABLE_RELAY) {
+     LOG(LS_VERBOSE) << "AllocationSequence: Relay ports disabled, skipping.";
+     return;
+  }
+
+  // If BasicPortAllocatorSession::OnAllocate left relay ports enabled then we
+  // ought to have a relay list for them here.
+  ASSERT(config_ && !config_->relays.empty());
+  if (!(config_ && !config_->relays.empty())) {
+    LOG(LS_WARNING)
+        << "AllocationSequence: No relay server configured, skipping.";
+    return;
+  }
+
+  PortConfiguration::RelayList::const_iterator relay;
+  for (relay = config_->relays.begin();
+       relay != config_->relays.end(); ++relay) {
+    RelayPort* port = RelayPort::Create(session_->network_thread(),
+                                        session_->allocator()->socket_factory(),
+                                        network_, ip_,
+                                        session_->allocator()->min_port(),
+                                        session_->allocator()->max_port(),
+                                        config_->username, config_->password,
+                                        config_->magic_cookie);
+    if (port) {
+      // Note: We must add the allocated port before we add addresses because
+      //       the latter will create candidates that need name and preference
+      //       settings.  However, we also can't prepare the address (normally
+      //       done by AddAllocatedPort) until we have these addresses.  So we
+      //       wait to do that until below.
+      session_->AddAllocatedPort(port, this, PREF_RELAY + relay->pref_modifier,
+                                 false);
+
+      // Add the addresses of this protocol.
+      PortConfiguration::PortList::const_iterator relay_port;
+      for (relay_port = relay->ports.begin();
+            relay_port != relay->ports.end();
+            ++relay_port) {
+        port->AddServerAddress(*relay_port);
+        port->AddExternalAddress(*relay_port);
+      }
+
+      // Start fetching an address for this port.
+      port->PrepareAddress();
+    }
+  }
+}
+
+// PortConfiguration
+PortConfiguration::PortConfiguration(const talk_base::SocketAddress& sa,
+                                     const std::string& un,
+                                     const std::string& pw,
+                                     const std::string& mc)
+    : stun_address(sa), username(un), password(pw), magic_cookie(mc) {
+}
+
+void PortConfiguration::AddRelay(const PortList& ports, float pref_modifier) {
+  RelayServer relay;
+  relay.ports = ports;
+  relay.pref_modifier = pref_modifier;
+  relays.push_back(relay);
+}
+
+bool PortConfiguration::ResolveStunAddress() {
+  int err = 0;
+  if (!stun_address.ResolveIP(true, &err)) {
+    LOG(LS_ERROR) << "Unable to resolve STUN host "
+                  << stun_address.hostname() << ".  Error " << err;
+    return false;
+  }
+  return true;
+}
+
+bool PortConfiguration::SupportsProtocol(
+    const PortConfiguration::RelayServer& relay, ProtocolType type) {
+  PortConfiguration::PortList::const_iterator relay_port;
+  for (relay_port = relay.ports.begin();
+        relay_port != relay.ports.end();
+        ++relay_port) {
+    if (relay_port->proto == type)
+      return true;
+  }
+  return false;
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/client/basicportallocator.h b/talk/p2p/client/basicportallocator.h
new file mode 100644
index 0000000..b2c1b8c
--- /dev/null
+++ b/talk/p2p/client/basicportallocator.h
@@ -0,0 +1,204 @@
+/*
+ * 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_P2P_CLIENT_BASICPORTALLOCATOR_H_
+#define TALK_P2P_CLIENT_BASICPORTALLOCATOR_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/messagequeue.h"
+#include "talk/base/network.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/portallocator.h"
+
+namespace cricket {
+
+class BasicPortAllocator : public PortAllocator {
+ public:
+  BasicPortAllocator(talk_base::NetworkManager* network_manager,
+                     talk_base::PacketSocketFactory* socket_factory);
+  BasicPortAllocator(talk_base::NetworkManager* network_manager,
+                     talk_base::PacketSocketFactory* socket_factory,
+                     const talk_base::SocketAddress& stun_server,
+                     const talk_base::SocketAddress& relay_server_udp,
+                     const talk_base::SocketAddress& relay_server_tcp,
+                     const talk_base::SocketAddress& relay_server_ssl);
+  virtual ~BasicPortAllocator();
+
+  talk_base::NetworkManager* network_manager() { return network_manager_; }
+
+  talk_base::PacketSocketFactory* socket_factory() { return socket_factory_; }
+
+  const talk_base::SocketAddress& stun_address() const {
+    return stun_address_;
+  }
+  const talk_base::SocketAddress& relay_address_udp() const {
+    return relay_address_udp_;
+  }
+  const talk_base::SocketAddress& relay_address_tcp() const {
+    return relay_address_tcp_;
+  }
+  const talk_base::SocketAddress& relay_address_ssl() const {
+    return relay_address_ssl_;
+  }
+
+  // Returns the best (highest preference) phase that has produced a port that
+  // produced a writable connection.  If no writable connections have been
+  // produced, this returns -1.
+  int best_writable_phase() const;
+
+  virtual PortAllocatorSession* CreateSession(const std::string& name,
+                                              const std::string& session_type);
+
+  // Called whenever a connection becomes writable with the argument being the
+  // phase that the corresponding port was created in.
+  void AddWritablePhase(int phase);
+
+  bool allow_tcp_listen() const {
+    return allow_tcp_listen_;
+  }
+  void set_allow_tcp_listen(bool allow_tcp_listen) {
+    allow_tcp_listen_ = allow_tcp_listen;
+  }
+
+ private:
+  talk_base::NetworkManager* network_manager_;
+  talk_base::PacketSocketFactory* socket_factory_;
+  const talk_base::SocketAddress stun_address_;
+  const talk_base::SocketAddress relay_address_udp_;
+  const talk_base::SocketAddress relay_address_tcp_;
+  const talk_base::SocketAddress relay_address_ssl_;
+  int best_writable_phase_;
+  bool allow_tcp_listen_;
+};
+
+struct PortConfiguration;
+class AllocationSequence;
+
+class BasicPortAllocatorSession : public PortAllocatorSession,
+    public talk_base::MessageHandler {
+ public:
+  BasicPortAllocatorSession(BasicPortAllocator* allocator,
+                            const std::string& name,
+                            const std::string& session_type);
+  ~BasicPortAllocatorSession();
+
+  virtual BasicPortAllocator* allocator() { return allocator_; }
+  const std::string& name() const { return name_; }
+  const std::string& session_type() const { return session_type_; }
+  talk_base::Thread* network_thread() { return network_thread_; }
+
+  virtual void GetInitialPorts();
+  virtual void StartGetAllPorts();
+  virtual void StopGetAllPorts();
+  virtual bool IsGettingAllPorts() { return running_; }
+
+ protected:
+  // Starts the process of getting the port configurations.
+  virtual void GetPortConfigurations();
+
+  // Adds a port configuration that is now ready.  Once we have one for each
+  // network (or a timeout occurs), we will start allocating ports.
+  void ConfigReady(PortConfiguration* config);
+
+  // MessageHandler.  Can be overriden if message IDs do not conflict.
+  virtual void OnMessage(talk_base::Message *message);
+
+ private:
+  void OnConfigReady(PortConfiguration* config);
+  void OnConfigTimeout();
+  void AllocatePorts();
+  void OnAllocate();
+  void DisableEquivalentPhases(talk_base::Network* network,
+      PortConfiguration* config, uint32* flags);
+  void AddAllocatedPort(Port* port, AllocationSequence* seq, float pref,
+      bool prepare_address = true);
+  void OnAddressReady(Port* port);
+  void OnProtocolEnabled(AllocationSequence* seq, ProtocolType proto);
+  void OnPortDestroyed(Port* port);
+  void OnConnectionCreated(Port* port, Connection* conn);
+  void OnConnectionStateChange(Connection* conn);
+  void OnShake();
+
+  BasicPortAllocator* allocator_;
+  std::string name_;
+  std::string session_type_;
+  talk_base::Thread* network_thread_;
+  bool configuration_done_;
+  bool allocation_started_;
+  bool running_;  // set when StartGetAllPorts is called
+  std::vector<PortConfiguration*> configs_;
+  std::vector<AllocationSequence*> sequences_;
+
+  struct PortData {
+    Port* port;
+    AllocationSequence* sequence;
+    bool ready;
+
+    bool operator==(Port* rhs) const { return (port == rhs); }
+  };
+  std::vector<PortData> ports_;
+
+  friend class AllocationSequence;
+};
+
+// Records configuration information useful in creating ports.
+struct PortConfiguration : public talk_base::MessageData {
+  talk_base::SocketAddress stun_address;
+  std::string username;
+  std::string password;
+  std::string magic_cookie;
+
+  typedef std::vector<ProtocolAddress> PortList;
+  struct RelayServer {
+    PortList ports;
+    float pref_modifier;  // added to the protocol modifier to get the
+                          // preference for this particular server
+  };
+
+  typedef std::vector<RelayServer> RelayList;
+  RelayList relays;
+
+  PortConfiguration(const talk_base::SocketAddress& stun_address,
+                    const std::string& username,
+                    const std::string& password,
+                    const std::string& magic_cookie);
+
+  // Adds another relay server, with the given ports and modifier, to the list.
+  void AddRelay(const PortList& ports, float pref_modifier);
+
+  bool ResolveStunAddress();
+
+  // Determines whether the given relay server supports the given protocol.
+  static bool SupportsProtocol(const PortConfiguration::RelayServer& relay,
+                               ProtocolType type);
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_CLIENT_BASICPORTALLOCATOR_H_
diff --git a/talk/p2p/client/httpportallocator.cc b/talk/p2p/client/httpportallocator.cc
new file mode 100644
index 0000000..4ef0825
--- /dev/null
+++ b/talk/p2p/client/httpportallocator.cc
@@ -0,0 +1,245 @@
+/*
+ * 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 "talk/p2p/client/httpportallocator.h"
+
+#include <map>
+
+#include "talk/base/asynchttprequest.h"
+#include "talk/base/basicdefs.h"
+#include "talk/base/common.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/nethelpers.h"
+#include "talk/base/signalthread.h"
+
+namespace {
+
+const uint32 MSG_TIMEOUT = 100;  // must not conflict
+  // with BasicPortAllocator.cpp
+
+// Helper routine to remove whitespace from the ends of a string.
+void Trim(std::string& str) {
+  size_t first = str.find_first_not_of(" \t\r\n");
+  if (first == std::string::npos) {
+    str.clear();
+    return;
+  }
+
+  ASSERT(str.find_last_not_of(" \t\r\n") != std::string::npos);
+}
+
+// Parses the lines in the result of the HTTP request that are of the form
+// 'a=b' and returns them in a map.
+typedef std::map<std::string, std::string> StringMap;
+void ParseMap(const std::string& string, StringMap& map) {
+  size_t start_of_line = 0;
+  size_t end_of_line = 0;
+
+  for (;;) {  // for each line
+    start_of_line = string.find_first_not_of("\r\n", end_of_line);
+    if (start_of_line == std::string::npos)
+      break;
+
+    end_of_line = string.find_first_of("\r\n", start_of_line);
+    if (end_of_line == std::string::npos) {
+      end_of_line = string.length();
+    }
+
+    size_t equals = string.find('=', start_of_line);
+    if ((equals >= end_of_line) || (equals == std::string::npos))
+      continue;
+
+    std::string key(string, start_of_line, equals - start_of_line);
+    std::string value(string, equals + 1, end_of_line - equals - 1);
+
+    Trim(key);
+    Trim(value);
+
+    if ((key.size() > 0) && (value.size() > 0))
+      map[key] = value;
+  }
+}
+
+}  // namespace
+
+namespace cricket {
+
+// HttpPortAllocator
+
+const int HttpPortAllocator::kHostPort = 80;
+const int HttpPortAllocator::kNumRetries = 5;
+
+const std::string HttpPortAllocator::kCreateSessionURL = "/create_session";
+
+HttpPortAllocator::HttpPortAllocator(
+    talk_base::NetworkManager* network_manager,
+    talk_base::PacketSocketFactory* socket_factory,
+    const std::string &user_agent)
+    : BasicPortAllocator(network_manager, socket_factory), agent_(user_agent) {
+  relay_hosts_.push_back("relay.google.com");
+  stun_hosts_.push_back(
+      talk_base::SocketAddress("stun.l.google.com", 19302));
+}
+
+HttpPortAllocator::~HttpPortAllocator() {
+}
+
+PortAllocatorSession *HttpPortAllocator::CreateSession(
+    const std::string& name, const std::string& session_type) {
+  return new HttpPortAllocatorSession(this, name, session_type, stun_hosts_,
+      relay_hosts_, relay_token_, agent_);
+}
+
+// HttpPortAllocatorSession
+
+HttpPortAllocatorSession::HttpPortAllocatorSession(
+    HttpPortAllocator* allocator, const std::string &name,
+    const std::string& session_type,
+    const std::vector<talk_base::SocketAddress>& stun_hosts,
+    const std::vector<std::string>& relay_hosts,
+    const std::string& relay_token,
+    const std::string& user_agent)
+    : BasicPortAllocatorSession(allocator, name, session_type),
+      relay_hosts_(relay_hosts), stun_hosts_(stun_hosts),
+      relay_token_(relay_token), agent_(user_agent), attempts_(0) {
+}
+
+void HttpPortAllocatorSession::GetPortConfigurations() {
+  // Creating relay sessions can take time and is done asynchronously.
+  // Creating stun sessions could also take time and could be done aysnc also,
+  // but for now is done here and added to the initial config.  Note any later
+  // configs will have unresolved stun ips and will be discarded by the
+  // AllocationSequence.
+  PortConfiguration* config = new PortConfiguration(stun_hosts_[0], "", "", "");
+  ConfigReady(config);
+  TryCreateRelaySession();
+}
+
+void HttpPortAllocatorSession::TryCreateRelaySession() {
+  if (attempts_ == HttpPortAllocator::kNumRetries) {
+    LOG(LS_ERROR) << "HttpPortAllocator: maximum number of requests reached; "
+                  << "giving up on relay.";
+    return;
+  }
+
+  if (relay_hosts_.size() == 0) {
+    LOG(LS_ERROR) << "HttpPortAllocator: no relay hosts configured.";
+    return;
+  }
+
+  // Choose the next host to try.
+  std::string host = relay_hosts_[attempts_ % relay_hosts_.size()];
+  attempts_++;
+  LOG(LS_INFO) << "HTTPPortAllocator: sending to relay host " << host;
+  if (relay_token_.empty()) {
+    LOG(LS_WARNING) << "No relay auth token found.";
+  }
+
+  SendSessionRequest(host, HttpPortAllocator::kHostPort);
+}
+
+void HttpPortAllocatorSession::SendSessionRequest(const std::string& host,
+                                                  int port) {
+  // Initiate an HTTP request to create a session through the chosen host.
+  talk_base::AsyncHttpRequest* request =
+      new talk_base::AsyncHttpRequest(agent_);
+  request->SignalWorkDone.connect(this,
+      &HttpPortAllocatorSession::OnRequestDone);
+
+  request->set_proxy(allocator()->proxy());
+  request->response().document.reset(new talk_base::MemoryStream);
+  request->request().verb = talk_base::HV_GET;
+  request->request().path = HttpPortAllocator::kCreateSessionURL;
+  request->request().addHeader("X-Talk-Google-Relay-Auth", relay_token_, true);
+  request->request().addHeader("X-Google-Relay-Auth", relay_token_, true);
+  request->request().addHeader("X-Session-Type", session_type(), true);
+  request->request().addHeader("X-Stream-Type", name(), true);
+  request->set_host(host);
+  request->set_port(port);
+  request->Start();
+  request->Release();
+}
+
+void HttpPortAllocatorSession::OnRequestDone(talk_base::SignalThread* data) {
+  talk_base::AsyncHttpRequest* request =
+      static_cast<talk_base::AsyncHttpRequest*>(data);
+  if (request->response().scode != 200) {
+    LOG(LS_WARNING) << "HTTPPortAllocator: request "
+                    << " received error " << request->response().scode;
+    TryCreateRelaySession();
+    return;
+  }
+  LOG(LS_INFO) << "HTTPPortAllocator: request succeeded";
+
+  talk_base::MemoryStream* stream =
+      static_cast<talk_base::MemoryStream*>(request->response().document.get());
+  stream->Rewind();
+  size_t length;
+  stream->GetSize(&length);
+  std::string resp = std::string(stream->GetBuffer(), length);
+  ReceiveSessionResponse(resp);
+}
+
+void HttpPortAllocatorSession::ReceiveSessionResponse(
+    const std::string& response) {
+
+  StringMap map;
+  ParseMap(response, map);
+
+  std::string username = map["username"];
+  std::string password = map["password"];
+  std::string magic_cookie = map["magic_cookie"];
+
+  std::string relay_ip = map["relay.ip"];
+  std::string relay_udp_port = map["relay.udp_port"];
+  std::string relay_tcp_port = map["relay.tcp_port"];
+  std::string relay_ssltcp_port = map["relay.ssltcp_port"];
+
+  PortConfiguration* config = new PortConfiguration(stun_hosts_[0],
+                                                    username,
+                                                    password,
+                                                    magic_cookie);
+
+  PortConfiguration::PortList ports;
+  if (!relay_udp_port.empty()) {
+    talk_base::SocketAddress address(relay_ip, atoi(relay_udp_port.c_str()));
+    ports.push_back(ProtocolAddress(address, PROTO_UDP));
+  }
+  if (!relay_tcp_port.empty()) {
+    talk_base::SocketAddress address(relay_ip, atoi(relay_tcp_port.c_str()));
+    ports.push_back(ProtocolAddress(address, PROTO_TCP));
+  }
+  if (!relay_ssltcp_port.empty()) {
+    talk_base::SocketAddress address(relay_ip, atoi(relay_ssltcp_port.c_str()));
+    ports.push_back(ProtocolAddress(address, PROTO_SSLTCP));
+  }
+  config->AddRelay(ports, 0.0f);
+  ConfigReady(config);
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/client/httpportallocator.h b/talk/p2p/client/httpportallocator.h
new file mode 100644
index 0000000..478f278
--- /dev/null
+++ b/talk/p2p/client/httpportallocator.h
@@ -0,0 +1,135 @@
+/*
+ * 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 TALK_P2P_CLIENT_HTTPPORTALLOCATOR_H_
+#define TALK_P2P_CLIENT_HTTPPORTALLOCATOR_H_
+
+#include <string>
+#include <vector>
+#include "talk/p2p/client/basicportallocator.h"
+
+namespace talk_base {
+class SignalThread;
+}
+
+namespace cricket {
+
+class HttpPortAllocator : public BasicPortAllocator {
+ public:
+  // Records the port on the hosts that will receive HTTP requests.
+  static const int kHostPort;
+
+  // The number of HTTP requests we should attempt before giving up.
+  static const int kNumRetries;
+
+  // Records the URL that we will GET in order to create a session.
+  static const std::string kCreateSessionURL;
+
+  HttpPortAllocator(talk_base::NetworkManager* network_manager,
+                    talk_base::PacketSocketFactory* socket_factory,
+                    const std::string& user_agent);
+  virtual ~HttpPortAllocator();
+
+  virtual PortAllocatorSession* CreateSession(const std::string& name,
+                                              const std::string& session_type);
+  void SetStunHosts(const std::vector<talk_base::SocketAddress>& hosts) {
+    if (!hosts.empty()) {
+      stun_hosts_ = hosts;
+    }
+  }
+  void SetRelayHosts(const std::vector<std::string>& hosts) {
+    if (!hosts.empty()) {
+      relay_hosts_ = hosts;
+    }
+  }
+  void SetRelayToken(const std::string& relay) { relay_token_ = relay; }
+
+  const std::vector<talk_base::SocketAddress>& stun_hosts() const {
+    return stun_hosts_;
+  }
+
+  const std::vector<std::string>& relay_hosts() const {
+    return relay_hosts_;
+  }
+
+  const std::string& relay_token() const {
+    return relay_token_;
+  }
+
+  const std::string& user_agent() const {
+    return agent_;
+  }
+
+ private:
+  std::vector<talk_base::SocketAddress> stun_hosts_;
+  std::vector<std::string> relay_hosts_;
+  std::string relay_token_;
+  std::string agent_;
+};
+
+class RequestData;
+
+class HttpPortAllocatorSession : public BasicPortAllocatorSession {
+ public:
+  HttpPortAllocatorSession(
+      HttpPortAllocator* allocator,
+      const std::string& name,
+      const std::string& session_type,
+      const std::vector<talk_base::SocketAddress>& stun_hosts,
+      const std::vector<std::string>& relay_hosts,
+      const std::string& relay,
+      const std::string& agent);
+  virtual ~HttpPortAllocatorSession() {}
+
+  const std::string& relay_token() const {
+    return relay_token_;
+  }
+  virtual void SendSessionRequest(const std::string& host, int port);
+  virtual void ReceiveSessionResponse(const std::string& response);
+
+ protected:
+  virtual void GetPortConfigurations();
+  void TryCreateRelaySession();
+
+ private:
+  virtual HttpPortAllocator* allocator() {
+    return static_cast<HttpPortAllocator*>(
+        BasicPortAllocatorSession::allocator());
+  }
+
+  void OnRequestDone(talk_base::SignalThread* request);
+
+  std::vector<std::string> relay_hosts_;
+  std::vector<talk_base::SocketAddress> stun_hosts_;
+  std::string relay_token_;
+  std::string agent_;
+  int attempts_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_CLIENT_HTTPPORTALLOCATOR_H_
diff --git a/talk/p2p/client/sessionmanagertask.h b/talk/p2p/client/sessionmanagertask.h
new file mode 100644
index 0000000..2026bf6
--- /dev/null
+++ b/talk/p2p/client/sessionmanagertask.h
@@ -0,0 +1,92 @@
+/*
+ * 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 _SESSIONMANAGERTASK_H_
+#define _SESSIONMANAGERTASK_H_
+
+#include "talk/p2p/base/sessionmanager.h"
+#include "talk/p2p/client/sessionsendtask.h"
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/xmpptask.h"
+
+namespace cricket {
+
+// This class handles sending and receiving XMPP messages on behalf of the
+// SessionManager.  The sending part is handed over to SessionSendTask.
+
+class SessionManagerTask : public buzz::XmppTask {
+ public:
+  SessionManagerTask(TaskParent *parent, SessionManager *session_manager)
+      : buzz::XmppTask(parent, buzz::XmppEngine::HL_SINGLE) {
+    session_manager_ = session_manager;
+  }
+
+  ~SessionManagerTask() {
+  }
+
+  // Turns on simple support for sending messages, using SessionSendTask.
+  void EnableOutgoingMessages() {
+    session_manager_->SignalOutgoingMessage.connect(
+        this, &SessionManagerTask::OnOutgoingMessage);
+    session_manager_->SignalRequestSignaling.connect(
+        session_manager_, &SessionManager::OnSignalingReady);
+  }
+
+  virtual int ProcessStart() {
+    const buzz::XmlElement *stanza = NextStanza();
+    if (stanza == NULL)
+      return STATE_BLOCKED;
+    session_manager_->OnIncomingMessage(stanza);
+    return STATE_START;
+  }
+
+ protected:
+  virtual bool HandleStanza(const buzz::XmlElement *stanza) {
+    if (!session_manager_->IsSessionMessage(stanza))
+      return false;
+    // Responses are handled by the SessionSendTask that sent the request.
+    //if (stanza->Attr(buzz::QN_TYPE) != buzz::STR_SET)
+    //  return false;
+    QueueStanza(stanza);
+    return true;
+  }
+
+ private:
+  SessionManager* session_manager_;
+
+  void OnOutgoingMessage(SessionManager* manager,
+                         const buzz::XmlElement* stanza) {
+    cricket::SessionSendTask* sender =
+        new cricket::SessionSendTask(GetParent(), session_manager_);
+    sender->Send(stanza);
+    sender->Start();
+  }
+};
+
+}  // namespace cricket
+
+#endif // _SESSIONMANAGERTASK_H_
diff --git a/talk/p2p/client/sessionsendtask.h b/talk/p2p/client/sessionsendtask.h
new file mode 100644
index 0000000..7bdc067
--- /dev/null
+++ b/talk/p2p/client/sessionsendtask.h
@@ -0,0 +1,137 @@
+/*
+ * 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 _CRICKET_P2P_CLIENT_SESSIONSENDTASK_H_
+#define _CRICKET_P2P_CLIENT_SESSIONSENDTASK_H_
+
+#include "talk/base/common.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/xmppclient.h"
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/xmpptask.h"
+#include "talk/p2p/base/sessionmanager.h"
+
+namespace cricket {
+
+// The job of this task is to send an IQ stanza out (after stamping it with
+// an ID attribute) and then wait for a response.  If not response happens
+// within 5 seconds, it will signal failure on a SessionManager.  If an error
+// happens it will also signal failure.  If, however, the send succeeds this
+// task will quietly go away.
+
+class SessionSendTask : public buzz::XmppTask {
+public:
+  SessionSendTask(TaskParent *parent, SessionManager *session_manager)
+    : buzz::XmppTask(parent, buzz::XmppEngine::HL_SINGLE),
+      session_manager_(session_manager) {
+    set_timeout_seconds(15);
+  }
+
+  virtual ~SessionSendTask() {
+    SignalDone(this);
+  }
+
+  void Send(const buzz::XmlElement* stanza) {
+    ASSERT(stanza_.get() == NULL);
+
+    // This should be an IQ of type set, result, or error.  In the first case,
+    // we supply an ID.  In the others, it should be present.
+    ASSERT(stanza->Name() == buzz::QN_IQ);
+    ASSERT(stanza->HasAttr(buzz::QN_TYPE));
+    if (stanza->Attr(buzz::QN_TYPE) == "set") {
+      ASSERT(!stanza->HasAttr(buzz::QN_ID));
+    } else {
+      ASSERT((stanza->Attr(buzz::QN_TYPE) == "result") ||
+             (stanza->Attr(buzz::QN_TYPE) == "error"));
+      ASSERT(stanza->HasAttr(buzz::QN_ID));
+    }
+
+    stanza_.reset(new buzz::XmlElement(*stanza));
+    if (stanza_->HasAttr(buzz::QN_ID)) {
+      set_task_id(stanza_->Attr(buzz::QN_ID));
+    } else {
+      stanza_->SetAttr(buzz::QN_ID, task_id());
+    }
+  }
+
+  sigslot::signal1<SessionSendTask *> SignalDone;
+
+protected:
+  virtual int OnTimeout() {
+    session_manager_->OnFailedSend(stanza_.get(), NULL);
+
+    return XmppTask::OnTimeout();
+  }
+
+  virtual int ProcessStart() {
+    SendStanza(stanza_.get());
+    if (stanza_->Attr(buzz::QN_TYPE) == buzz::STR_SET) {
+      return STATE_RESPONSE;
+    } else {
+      return STATE_DONE;
+    }
+  }
+
+  virtual int ProcessResponse() {
+    if (GetClient()->GetState() != buzz::XmppEngine::STATE_OPEN) {
+      return STATE_DONE;
+    }
+
+    const buzz::XmlElement* next = NextStanza();
+    if (next == NULL)
+      return STATE_BLOCKED;
+
+    if (next->Attr(buzz::QN_TYPE) == buzz::STR_RESULT) {
+      session_manager_->OnIncomingResponse(stanza_.get(), next);
+    } else {
+      session_manager_->OnFailedSend(stanza_.get(), next);
+    }
+
+    return STATE_DONE;
+  }
+
+  virtual bool HandleStanza(const buzz::XmlElement *stanza) {
+    if (!MatchResponseIq(stanza,
+                         buzz::Jid(stanza_->Attr(buzz::QN_TO)), task_id()))
+      return false;
+    if (stanza->Attr(buzz::QN_TYPE) == buzz::STR_RESULT ||
+        stanza->Attr(buzz::QN_TYPE) == buzz::STR_ERROR) {
+      QueueStanza(stanza);
+      return true;
+    }
+    return false;
+  }
+
+private:
+  SessionManager *session_manager_;
+  talk_base::scoped_ptr<buzz::XmlElement> stanza_;
+  bool timed_out_;
+};
+
+}
+
+#endif // _CRICKET_P2P_CLIENT_SESSIONSENDTASK_H_
diff --git a/talk/p2p/client/socketmonitor.cc b/talk/p2p/client/socketmonitor.cc
new file mode 100644
index 0000000..bf32d84
--- /dev/null
+++ b/talk/p2p/client/socketmonitor.cc
@@ -0,0 +1,154 @@
+/*
+ * 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/p2p/client/socketmonitor.h"
+#include "talk/base/common.h"
+
+namespace cricket {
+
+const uint32 MSG_MONITOR_POLL = 1;
+const uint32 MSG_MONITOR_START = 2;
+const uint32 MSG_MONITOR_STOP = 3;
+const uint32 MSG_MONITOR_SIGNAL = 4;
+
+SocketMonitor::SocketMonitor(TransportChannel* channel,
+                             talk_base::Thread* worker_thread,
+                             talk_base::Thread* monitor_thread) {
+  channel_ = channel;
+  channel_thread_ = worker_thread;
+  monitoring_thread_ = monitor_thread;
+  monitoring_ = false;
+}
+
+SocketMonitor::~SocketMonitor() {
+  channel_thread_->Clear(this);
+  monitoring_thread_->Clear(this);
+}
+
+void SocketMonitor::Start(int milliseconds) {
+  rate_ = milliseconds;
+  if (rate_ < 250)
+    rate_ = 250;
+  channel_thread_->Post(this, MSG_MONITOR_START);
+}
+
+void SocketMonitor::Stop() {
+  channel_thread_->Post(this, MSG_MONITOR_STOP);
+}
+
+void SocketMonitor::OnMessage(talk_base::Message *message) {
+  talk_base::CritScope cs(&crit_);
+
+  switch (message->message_id) {
+  case MSG_MONITOR_START:
+    ASSERT(talk_base::Thread::Current() == channel_thread_);
+    if (!monitoring_) {
+      monitoring_ = true;
+      if (GetP2PChannel() != NULL) {
+        GetP2PChannel()->SignalConnectionMonitor.connect(
+            this, &SocketMonitor::OnConnectionMonitor);
+      }
+      PollSocket(true);
+    }
+    break;
+
+  case MSG_MONITOR_STOP:
+    ASSERT(talk_base::Thread::Current() == channel_thread_);
+    if (monitoring_) {
+      monitoring_ = false;
+      if (GetP2PChannel() != NULL)
+        GetP2PChannel()->SignalConnectionMonitor.disconnect(this);
+      channel_thread_->Clear(this);
+    }
+    break;
+
+  case MSG_MONITOR_POLL:
+    ASSERT(talk_base::Thread::Current() == channel_thread_);
+    PollSocket(true);
+    break;
+
+  case MSG_MONITOR_SIGNAL:
+    {
+      ASSERT(talk_base::Thread::Current() == monitoring_thread_);
+      std::vector<ConnectionInfo> infos = connection_infos_;
+      crit_.Leave();
+      SignalUpdate(this, infos);
+      crit_.Enter();
+    }
+    break;
+  }
+}
+
+void SocketMonitor::OnConnectionMonitor(P2PTransportChannel* channel) {
+  talk_base::CritScope cs(&crit_);
+  if (monitoring_)
+    PollSocket(false);
+}
+
+void SocketMonitor::PollSocket(bool poll) {
+  ASSERT(talk_base::Thread::Current() == channel_thread_);
+  talk_base::CritScope cs(&crit_);
+
+  // Gather connection infos
+  P2PTransportChannel* p2p_channel = GetP2PChannel();
+  if (p2p_channel != NULL) {
+    connection_infos_.clear();
+    const std::vector<Connection *> &connections = p2p_channel->connections();
+    std::vector<Connection *>::const_iterator it;
+    for (it = connections.begin(); it != connections.end(); it++) {
+      Connection *connection = *it;
+      ConnectionInfo info;
+      info.best_connection = p2p_channel->best_connection() == connection;
+      info.readable =
+          (connection->read_state() == Connection::STATE_READABLE);
+      info.writable =
+          (connection->write_state() == Connection::STATE_WRITABLE);
+      info.timeout =
+          (connection->write_state() == Connection::STATE_WRITE_TIMEOUT);
+      info.new_connection = !connection->reported();
+      connection->set_reported(true);
+      info.rtt = connection->rtt();
+      info.sent_total_bytes = connection->sent_total_bytes();
+      info.sent_bytes_second = connection->sent_bytes_second();
+      info.recv_total_bytes = connection->recv_total_bytes();
+      info.recv_bytes_second = connection->recv_bytes_second();
+      info.local_candidate = connection->local_candidate();
+      info.remote_candidate = connection->remote_candidate();
+      info.est_quality = connection->port()->network()->quality();
+      info.key = connection;
+      connection_infos_.push_back(info);
+    }
+  }
+
+  // Signal the monitoring thread, start another poll timer
+
+  monitoring_thread_->Post(this, MSG_MONITOR_SIGNAL);
+  if (poll)
+    channel_thread_->PostDelayed(rate_, this, MSG_MONITOR_POLL);
+}
+
+}
diff --git a/talk/p2p/client/socketmonitor.h b/talk/p2p/client/socketmonitor.h
new file mode 100644
index 0000000..cf29d9d
--- /dev/null
+++ b/talk/p2p/client/socketmonitor.h
@@ -0,0 +1,91 @@
+/*
+ * 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_P2P_CLIENT_SOCKETMONITOR_H_
+#define TALK_P2P_CLIENT_SOCKETMONITOR_H_
+
+#include <vector>
+
+#include "talk/base/criticalsection.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/p2ptransportchannel.h"
+#include "talk/p2p/base/port.h"
+
+namespace cricket {
+
+struct ConnectionInfo {
+  bool best_connection;
+  bool writable;
+  bool readable;
+  bool timeout;
+  bool new_connection;
+  size_t rtt;
+  size_t sent_total_bytes;
+  size_t sent_bytes_second;
+  size_t recv_total_bytes;
+  size_t recv_bytes_second;
+  Candidate local_candidate;
+  Candidate remote_candidate;
+  double est_quality;
+  void *key;
+};
+
+class SocketMonitor : public talk_base::MessageHandler,
+    public sigslot::has_slots<> {
+ public:
+  SocketMonitor(TransportChannel* channel,
+                talk_base::Thread* worker_thread,
+                talk_base::Thread* monitor_thread);
+  ~SocketMonitor();
+
+  void Start(int cms);
+  void Stop();
+
+  talk_base::Thread *monitor_thread() { return monitoring_thread_; }
+
+  sigslot::signal2<SocketMonitor *,
+                   const std::vector<ConnectionInfo> &> SignalUpdate;
+
+ protected:
+  P2PTransportChannel* GetP2PChannel() { return channel_->GetP2PChannel(); }
+  void OnMessage(talk_base::Message *message);
+  void OnConnectionMonitor(P2PTransportChannel* channel);
+  void PollSocket(bool poll);
+
+  std::vector<ConnectionInfo> connection_infos_;
+  TransportChannel* channel_;
+  talk_base::Thread* channel_thread_;
+  talk_base::Thread* monitoring_thread_;
+  talk_base::CriticalSection crit_;
+  uint32 rate_;
+  bool monitoring_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_CLIENT_SOCKETMONITOR_H_
diff --git a/talk/session/phone/audiomonitor.cc b/talk/session/phone/audiomonitor.cc
new file mode 100644
index 0000000..d980ded
--- /dev/null
+++ b/talk/session/phone/audiomonitor.cc
@@ -0,0 +1,121 @@
+/*
+ * 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/session/phone/audiomonitor.h"
+#include "talk/session/phone/voicechannel.h"
+#include <cassert>
+
+namespace cricket {
+
+const uint32 MSG_MONITOR_POLL = 1;
+const uint32 MSG_MONITOR_START = 2;
+const uint32 MSG_MONITOR_STOP = 3;
+const uint32 MSG_MONITOR_SIGNAL = 4;
+
+AudioMonitor::AudioMonitor(VoiceChannel *voice_channel, 
+                           talk_base::Thread *monitor_thread) {
+  voice_channel_ = voice_channel;
+  monitoring_thread_ = monitor_thread;
+  monitoring_ = false;
+}
+
+AudioMonitor::~AudioMonitor() {
+  voice_channel_->worker_thread()->Clear(this);
+  monitoring_thread_->Clear(this);
+}
+
+void AudioMonitor::Start(int milliseconds) {
+  rate_ = milliseconds;
+  if (rate_ < 100)
+    rate_ = 100;
+  voice_channel_->worker_thread()->Post(this, MSG_MONITOR_START);
+}
+
+void AudioMonitor::Stop() {
+  voice_channel_->worker_thread()->Post(this, MSG_MONITOR_STOP);
+}
+
+void AudioMonitor::OnMessage(talk_base::Message *message) {
+  talk_base::CritScope cs(&crit_);
+
+  switch (message->message_id) {
+  case MSG_MONITOR_START:
+    assert(talk_base::Thread::Current() == voice_channel_->worker_thread());
+    if (!monitoring_) {
+      monitoring_ = true;
+      PollVoiceChannel();
+    }
+    break;
+
+  case MSG_MONITOR_STOP:
+    assert(talk_base::Thread::Current() == voice_channel_->worker_thread());
+    if (monitoring_) {
+      monitoring_ = false;
+      voice_channel_->worker_thread()->Clear(this);
+    }
+    break;
+
+  case MSG_MONITOR_POLL:
+    assert(talk_base::Thread::Current() == voice_channel_->worker_thread());
+    PollVoiceChannel();
+    break;
+
+  case MSG_MONITOR_SIGNAL:
+    {
+      assert(talk_base::Thread::Current() == monitoring_thread_);
+      AudioInfo info = audio_info_;
+      crit_.Leave();
+      SignalUpdate(this, info);
+      crit_.Enter();
+    }
+    break;
+  }
+}
+
+void AudioMonitor::PollVoiceChannel() {
+  talk_base::CritScope cs(&crit_);
+  assert(talk_base::Thread::Current() == voice_channel_->worker_thread());
+
+  // Gather connection infos
+  audio_info_.input_level = voice_channel_->GetInputLevel_w();
+  audio_info_.output_level = voice_channel_->GetOutputLevel_w();
+  voice_channel_->GetActiveStreams_w(&audio_info_.active_streams);
+
+  // Signal the monitoring thread, start another poll timer
+  monitoring_thread_->Post(this, MSG_MONITOR_SIGNAL);
+  voice_channel_->worker_thread()->PostDelayed(rate_, this, MSG_MONITOR_POLL);
+}
+
+VoiceChannel *AudioMonitor::voice_channel() {
+  return voice_channel_;
+}
+
+talk_base::Thread *AudioMonitor::monitor_thread() {
+  return monitoring_thread_;
+}
+
+}
diff --git a/talk/session/phone/audiomonitor.h b/talk/session/phone/audiomonitor.h
new file mode 100644
index 0000000..e94ea68
--- /dev/null
+++ b/talk/session/phone/audiomonitor.h
@@ -0,0 +1,75 @@
+/*
+ * 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 _CRICKET_PHONE_AUDIOMONITOR_H_
+#define _CRICKET_PHONE_AUDIOMONITOR_H_
+
+#include "talk/base/thread.h"
+#include "talk/base/sigslot.h"
+#include "talk/p2p/base/port.h"
+#include <vector>
+
+namespace cricket {
+
+class VoiceChannel;
+
+struct AudioInfo {
+  int input_level;
+  int output_level;
+  typedef std::vector<std::pair<uint32, int> > StreamList;
+  StreamList active_streams; // ssrcs contributing to output_level
+};
+
+class AudioMonitor : public talk_base::MessageHandler, 
+    public sigslot::has_slots<> {
+public:
+  AudioMonitor(VoiceChannel* voice_channel, talk_base::Thread *monitor_thread);
+  ~AudioMonitor();
+
+  void Start(int cms);
+  void Stop();
+
+  VoiceChannel* voice_channel();
+  talk_base::Thread *monitor_thread();
+
+  sigslot::signal2<AudioMonitor*, const AudioInfo&> SignalUpdate;
+
+protected:
+  void OnMessage(talk_base::Message *message);
+  void PollVoiceChannel();
+
+  AudioInfo audio_info_;
+  VoiceChannel* voice_channel_;
+  talk_base::Thread* monitoring_thread_;
+  talk_base::CriticalSection crit_;
+  uint32 rate_;
+  bool monitoring_;
+};
+
+}
+
+#endif // _CRICKET_PHONE_AUDIOMONITOR_H_
diff --git a/talk/session/phone/call.cc b/talk/session/phone/call.cc
new file mode 100644
index 0000000..fa4d33d
--- /dev/null
+++ b/talk/session/phone/call.cc
@@ -0,0 +1,499 @@
+/*
+ * 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 <string>
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/thread.h"
+#include "talk/session/phone/call.h"
+#include "talk/session/phone/mediasessionclient.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;
+}
+
+Call::Call(MediaSessionClient* session_client)
+    : id_(talk_base::CreateRandomId()),
+      session_client_(session_client),
+      local_renderer_(NULL),
+      muted_(false),
+      send_to_voicemail_(true),
+      playing_dtmf_(false) {
+}
+
+Call::~Call() {
+  while (sessions_.begin() != sessions_.end()) {
+    Session *session = sessions_[0];
+    RemoveSession(session);
+    session_client_->session_manager()->DestroySession(session);
+  }
+  talk_base::Thread::Current()->Clear(this);
+}
+
+Session *Call::InitiateSession(const buzz::Jid &jid,
+                               const CallOptions& options) {
+  const SessionDescription* offer = session_client_->CreateOffer(options);
+
+  Session *session = session_client_->CreateSession(this);
+  AddSession(session, offer);
+  session->Initiate(jid.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;
+}
+
+void Call::IncomingSession(
+    Session* session, const SessionDescription* offer) {
+  AddSession(session, offer);
+
+  // Missed the first state, the initiate, which is needed by
+  // call_client.
+  SignalSessionState(this, session, Session::STATE_RECEIVEDINITIATE);
+}
+
+void Call::AcceptSession(BaseSession* session,
+                         const cricket::CallOptions& options) {
+  std::vector<Session *>::iterator it;
+  it = std::find(sessions_.begin(), sessions_.end(), session);
+  ASSERT(it != sessions_.end());
+  if (it != sessions_.end()) {
+    session->Accept(
+        session_client_->CreateAnswer(session->remote_description(), options));
+  }
+}
+
+void Call::RejectSession(BaseSession *session) {
+  std::vector<Session *>::iterator it;
+  it = std::find(sessions_.begin(), sessions_.end(), session);
+  ASSERT(it != sessions_.end());
+  // Assume polite decline.
+  if (it != sessions_.end())
+    session->Reject(STR_TERMINATE_DECLINE);
+}
+
+void Call::TerminateSession(BaseSession *session) {
+  ASSERT(std::find(sessions_.begin(), sessions_.end(), session)
+         != sessions_.end());
+  std::vector<Session *>::iterator it;
+  it = std::find(sessions_.begin(), sessions_.end(), session);
+  // Assume polite terminations.
+  if (it != sessions_.end())
+    (*it)->Terminate();
+}
+
+void Call::Terminate() {
+  // Copy the list so that we can iterate over it in a stable way
+  std::vector<Session *> sessions = 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);
+}
+
+void Call::SetLocalRenderer(VideoRenderer* renderer) {
+  local_renderer_ = renderer;
+  if (session_client_->GetFocus() == this) {
+    session_client_->channel_manager()->SetLocalRenderer(renderer);
+  }
+}
+
+void Call::SetVideoRenderer(BaseSession *session, uint32 ssrc,
+                            VideoRenderer* renderer) {
+  VideoChannel *video_channel = GetVideoChannel(session);
+  if (video_channel) {
+    video_channel->SetRenderer(ssrc, renderer);
+  }
+}
+
+void Call::AddStream(BaseSession *session,
+                     uint32 voice_ssrc, uint32 video_ssrc) {
+  VoiceChannel *voice_channel = GetVoiceChannel(session);
+  VideoChannel *video_channel = GetVideoChannel(session);
+  if (voice_channel && voice_ssrc) {
+    voice_channel->AddStream(voice_ssrc);
+  }
+  if (video_channel && video_ssrc) {
+    video_channel->AddStream(video_ssrc, voice_ssrc);
+  }
+}
+
+void Call::RemoveStream(BaseSession *session,
+                        uint32 voice_ssrc, uint32 video_ssrc) {
+  VoiceChannel *voice_channel = GetVoiceChannel(session);
+  VideoChannel *video_channel = GetVideoChannel(session);
+  if (voice_channel && voice_ssrc) {
+    voice_channel->RemoveStream(voice_ssrc);
+  }
+  if (video_channel && video_ssrc) {
+    video_channel->RemoveStream(video_ssrc);
+  }
+}
+
+void Call::OnMessage(talk_base::Message *message) {
+  switch (message->message_id) {
+  case MSG_CHECKAUTODESTROY:
+    // If no more sessions for this call, delete it
+    if (sessions_.size() == 0)
+      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();
+  }
+}
+
+const std::vector<Session *> &Call::sessions() {
+  return sessions_;
+}
+
+bool Call::AddSession(Session *session, const SessionDescription* offer) {
+  bool succeeded = true;
+  VoiceChannel *voice_channel = NULL;
+  VideoChannel *video_channel = NULL;
+
+  const ContentInfo* audio_offer = GetFirstAudioContent(offer);
+  const ContentInfo* video_offer = GetFirstVideoContent(offer);
+  video_ = (video_offer != NULL);
+
+  ASSERT(audio_offer != NULL);
+  // Create voice channel and start a media monitor
+  voice_channel = session_client_->channel_manager()->CreateVoiceChannel(
+      session, audio_offer->name, video_);
+  // voice_channel can be NULL in case of NullVoiceEngine.
+  if (voice_channel) {
+    voice_channel_map_[session->id()] = voice_channel;
+
+    voice_channel->SignalMediaMonitor.connect(this, &Call::OnMediaMonitor);
+    voice_channel->StartMediaMonitor(kMediaMonitorInterval);
+  } else {
+    succeeded = false;
+  }
+
+  // If desired, create video channel and start a media monitor
+  if (video_ && succeeded) {
+    video_channel = session_client_->channel_manager()->CreateVideoChannel(
+        session, video_offer->name, true, voice_channel);
+    // video_channel can be NULL in case of NullVideoEngine.
+    if (video_channel) {
+      video_channel_map_[session->id()] = video_channel;
+
+      video_channel->SignalMediaMonitor.connect(this, &Call::OnMediaMonitor);
+      video_channel->StartMediaMonitor(kMediaMonitorInterval);
+    } else {
+      succeeded = false;
+    }
+  }
+
+  if (succeeded) {
+    // Add session to list, create channels for this session
+    sessions_.push_back(session);
+    session->SignalState.connect(this, &Call::OnSessionState);
+    session->SignalError.connect(this, &Call::OnSessionError);
+    session->SignalReceivedTerminateReason
+      .connect(this, &Call::OnReceivedTerminateReason);
+
+    // If this call has the focus, enable this channel
+    if (session_client_->GetFocus() == this) {
+      voice_channel->Enable(true);
+      if (video_channel) {
+        video_channel->Enable(true);
+      }
+    }
+
+    // Signal client
+    SignalAddSession(this, session);
+  }
+
+  return succeeded;
+}
+
+void Call::RemoveSession(Session *session) {
+  // Remove session from list
+  std::vector<Session *>::iterator it_session;
+  it_session = std::find(sessions_.begin(), sessions_.end(), session);
+  if (it_session == sessions_.end())
+    return;
+  sessions_.erase(it_session);
+
+  // Destroy video channel
+  std::map<std::string, VideoChannel *>::iterator it_vchannel;
+  it_vchannel = video_channel_map_.find(session->id());
+  if (it_vchannel != video_channel_map_.end()) {
+    VideoChannel *video_channel = it_vchannel->second;
+    video_channel_map_.erase(it_vchannel);
+    session_client_->channel_manager()->DestroyVideoChannel(video_channel);
+  }
+
+  // Destroy voice channel
+  std::map<std::string, VoiceChannel *>::iterator it_channel;
+  it_channel = voice_channel_map_.find(session->id());
+  if (it_channel != voice_channel_map_.end()) {
+    VoiceChannel *voice_channel = it_channel->second;
+    voice_channel_map_.erase(it_channel);
+    session_client_->channel_manager()->DestroyVoiceChannel(voice_channel);
+  }
+
+  // Signal client
+  SignalRemoveSession(this, session);
+
+  // The call auto destroys when the last session is removed
+  talk_base::Thread::Current()->Post(this, MSG_CHECKAUTODESTROY);
+}
+
+VoiceChannel* Call::GetVoiceChannel(BaseSession* session) {
+  std::map<std::string, VoiceChannel *>::iterator it
+    = voice_channel_map_.find(session->id());
+  return (it != voice_channel_map_.end()) ? it->second : NULL;
+}
+
+VideoChannel* Call::GetVideoChannel(BaseSession* session) {
+  std::map<std::string, VideoChannel *>::iterator it
+    = video_channel_map_.find(session->id());
+  return (it != video_channel_map_.end()) ? it->second : NULL;
+}
+
+void Call::EnableChannels(bool enable) {
+  std::vector<Session *>::iterator it;
+  for (it = sessions_.begin(); it != sessions_.end(); it++) {
+    VoiceChannel *voice_channel = GetVoiceChannel(*it);
+    VideoChannel *video_channel = GetVideoChannel(*it);
+    if (voice_channel != NULL)
+      voice_channel->Enable(enable);
+    if (video_channel != NULL)
+      video_channel->Enable(enable);
+  }
+  session_client_->channel_manager()->SetLocalRenderer(
+      (enable) ? local_renderer_ : NULL);
+}
+
+void Call::Mute(bool mute) {
+  muted_ = mute;
+  std::vector<Session *>::iterator it;
+  for (it = sessions_.begin(); it != sessions_.end(); it++) {
+    VoiceChannel *voice_channel = voice_channel_map_[(*it)->id()];
+    if (voice_channel != NULL)
+      voice_channel->Mute(mute);
+  }
+}
+
+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();
+    }
+  }
+}
+
+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 << ")";
+    std::vector<Session *>::iterator it;
+    for (it = sessions_.begin(); it != sessions_.end(); it++) {
+      VoiceChannel *voice_channel = voice_channel_map_[(*it)->id()];
+      if (voice_channel != NULL) {
+        voice_channel->PressDTMF(tone, true);
+      }
+    }
+
+    // Post a message to play the next tone or at least clear the playing_dtmf_
+    // bit.
+    talk_base::Thread::Current()->PostDelayed(kDTMFDelay, this, MSG_PLAYDTMF);
+  }
+}
+
+void Call::Join(Call *call, bool enable) {
+  while (call->sessions_.size() != 0) {
+    // Move session
+    Session *session = call->sessions_[0];
+    call->sessions_.erase(call->sessions_.begin());
+    sessions_.push_back(session);
+    session->SignalState.connect(this, &Call::OnSessionState);
+    session->SignalError.connect(this, &Call::OnSessionError);
+    session->SignalReceivedTerminateReason
+      .connect(this, &Call::OnReceivedTerminateReason);
+
+    // Move voice channel
+    std::map<std::string, VoiceChannel *>::iterator it_channel;
+    it_channel = call->voice_channel_map_.find(session->id());
+    if (it_channel != call->voice_channel_map_.end()) {
+      VoiceChannel *voice_channel = (*it_channel).second;
+      call->voice_channel_map_.erase(it_channel);
+      voice_channel_map_[session->id()] = voice_channel;
+      voice_channel->Enable(enable);
+    }
+
+    // Move video channel
+    std::map<std::string, VideoChannel *>::iterator it_vchannel;
+    it_vchannel = call->video_channel_map_.find(session->id());
+    if (it_vchannel != call->video_channel_map_.end()) {
+      VideoChannel *video_channel = (*it_vchannel).second;
+      call->video_channel_map_.erase(it_vchannel);
+      video_channel_map_[session->id()] = video_channel;
+      video_channel->Enable(enable);
+    }
+  }
+}
+
+void Call::StartConnectionMonitor(BaseSession *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(BaseSession *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(BaseSession *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(BaseSession *session) {
+  VoiceChannel *voice_channel = GetVoiceChannel(session);
+  if (voice_channel) {
+    voice_channel->StopAudioMonitor();
+    voice_channel->SignalAudioMonitor.disconnect(this);
+  }
+}
+
+void Call::OnConnectionMonitor(VoiceChannel *channel,
+                               const std::vector<ConnectionInfo> &infos) {
+  SignalConnectionMonitor(this, infos);
+}
+
+void Call::OnMediaMonitor(VoiceChannel *channel, const VoiceMediaInfo& info) {
+  SignalMediaMonitor(this, info);
+}
+
+void Call::OnAudioMonitor(VoiceChannel *channel, const AudioInfo& info) {
+  SignalAudioMonitor(this, info);
+}
+
+void Call::OnConnectionMonitor(VideoChannel *channel,
+                               const std::vector<ConnectionInfo> &infos) {
+  SignalVideoConnectionMonitor(this, infos);
+}
+
+void Call::OnMediaMonitor(VideoChannel *channel, const VideoMediaInfo& info) {
+  SignalVideoMediaMonitor(this, info);
+}
+
+uint32 Call::id() {
+  return id_;
+}
+
+void Call::OnSessionState(BaseSession *session, BaseSession::State state) {
+  switch (state) {
+    case Session::STATE_RECEIVEDACCEPT:
+    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 *session, Session::Error error) {
+  session_client_->session_manager()->signaling_thread()->Clear(this,
+      MSG_TERMINATECALL);
+  SignalSessionError(this, session, error);
+}
+
+void Call::OnReceivedTerminateReason(Session *session,
+                                     const std::string &reason) {
+  session_client_->session_manager()->signaling_thread()->Clear(this,
+    MSG_TERMINATECALL);
+  SignalReceivedTerminateReason(this, session, reason);
+}
+
+}  // namespace cricket
diff --git a/talk/session/phone/call.h b/talk/session/phone/call.h
new file mode 100644
index 0000000..8e9bcdc
--- /dev/null
+++ b/talk/session/phone/call.h
@@ -0,0 +1,144 @@
+/*
+ * 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_SESSION_PHONE_CALL_H_
+#define TALK_SESSION_PHONE_CALL_H_
+
+#include <string>
+#include <map>
+#include <vector>
+#include <deque>
+#include "talk/base/messagequeue.h"
+#include "talk/p2p/base/session.h"
+#include "talk/p2p/client/socketmonitor.h"
+#include "talk/xmpp/jid.h"
+#include "talk/session/phone/audiomonitor.h"
+#include "talk/session/phone/voicechannel.h"
+
+namespace cricket {
+
+class MediaSessionClient;
+struct CallOptions;
+
+class Call : public talk_base::MessageHandler, public sigslot::has_slots<> {
+ public:
+  Call(MediaSessionClient* session_client);
+  ~Call();
+
+  Session *InitiateSession(const buzz::Jid &jid, const CallOptions& options);
+  void AcceptSession(BaseSession *session, const CallOptions& options);
+  void RejectSession(BaseSession *session);
+  void TerminateSession(BaseSession *session);
+  void Terminate();
+  void SetLocalRenderer(VideoRenderer* renderer);
+  void SetVideoRenderer(BaseSession *session, uint32 ssrc,
+                        VideoRenderer* renderer);
+  void AddStream(BaseSession *session, uint32 voice_ssrc, uint32 video_ssrc);
+  void RemoveStream(BaseSession *session, uint32 voice_ssrc, uint32 video_ssrc);
+  void StartConnectionMonitor(BaseSession *session, int cms);
+  void StopConnectionMonitor(BaseSession *session);
+  void StartAudioMonitor(BaseSession *session, int cms);
+  void StopAudioMonitor(BaseSession *session);
+  void Mute(bool mute);
+  void PressDTMF(int event);
+
+  const std::vector<Session *> &sessions();
+  uint32 id();
+  bool video() const { return video_; }
+  bool muted() const { return muted_; }
+
+  // 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_; }
+
+  // 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 *, BaseSession *, BaseSession::State>
+      SignalSessionState;
+  sigslot::signal3<Call *, BaseSession *, 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;
+  sigslot::signal2<Call *, const std::vector<ConnectionInfo> &>
+      SignalVideoConnectionMonitor;
+  sigslot::signal2<Call *, const VideoMediaInfo&> SignalVideoMediaMonitor;
+
+ private:
+  void OnMessage(talk_base::Message *message);
+  void OnSessionState(BaseSession *session, BaseSession::State state);
+  void OnSessionError(BaseSession *session, Session::Error error);
+  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 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 OnConnectionMonitor(VideoChannel *channel,
+                           const std::vector<ConnectionInfo> &infos);
+  void OnMediaMonitor(VideoChannel *channel, const VideoMediaInfo& info);
+  VoiceChannel* GetVoiceChannel(BaseSession* session);
+  VideoChannel* GetVideoChannel(BaseSession* session);
+  void ContinuePlayDTMF();
+
+  uint32 id_;
+  MediaSessionClient *session_client_;
+  std::vector<Session *> sessions_;
+  std::map<std::string, VoiceChannel *> voice_channel_map_;
+  std::map<std::string, VideoChannel *> video_channel_map_;
+  VideoRenderer* local_renderer_;
+  bool video_;
+  bool 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_;
+
+  friend class MediaSessionClient;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_CALL_H_
diff --git a/talk/session/phone/channel.cc b/talk/session/phone/channel.cc
new file mode 100644
index 0000000..1083ec7
--- /dev/null
+++ b/talk/session/phone/channel.cc
@@ -0,0 +1,1164 @@
+/*
+ * 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/session/phone/channel.h"
+
+#include "talk/base/buffer.h"
+#include "talk/base/byteorder.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/p2p/base/transportchannel.h"
+#include "talk/session/phone/channelmanager.h"
+#include "talk/session/phone/mediasessionclient.h"
+#include "talk/session/phone/mediasink.h"
+#include "talk/session/phone/rtcpmuxfilter.h"
+
+namespace cricket {
+
+struct PacketMessageData : public talk_base::MessageData {
+  talk_base::Buffer packet;
+};
+
+struct VoiceChannelErrorMessageData : public talk_base::MessageData {
+  VoiceChannelErrorMessageData(uint32 in_ssrc,
+                               VoiceMediaChannel::Error in_error)
+      : ssrc(in_ssrc),
+        error(in_error) {}
+  uint32 ssrc;
+  VoiceMediaChannel::Error error;
+};
+
+struct VideoChannelErrorMessageData : public talk_base::MessageData {
+  VideoChannelErrorMessageData(uint32 in_ssrc,
+                               VideoMediaChannel::Error in_error)
+      : ssrc(in_ssrc),
+        error(in_error) {}
+  uint32 ssrc;
+  VideoMediaChannel::Error error;
+};
+
+static const char* PacketType(bool rtcp) {
+  return (!rtcp) ? "RTP" : "RTCP";
+}
+
+static bool ValidPacket(bool rtcp, const talk_base::Buffer* packet) {
+  // Check the packet size. We could check the header too if needed.
+  return (packet &&
+      packet->length() >= (!rtcp ? kMinRtpPacketLen : kMinRtcpPacketLen) &&
+      packet->length() <= kMaxRtpPacketLen);
+}
+
+static uint16 GetRtpSeqNum(const talk_base::Buffer* packet) {
+  return (packet->length() >= kMinRtpPacketLen) ?
+       talk_base::GetBE16(packet->data() + 2) : 0;
+}
+
+static uint32 GetRtpSsrc(const talk_base::Buffer* packet) {
+  return (packet->length() >= kMinRtpPacketLen) ?
+       talk_base::GetBE32(packet->data() + 8) : 0;
+}
+
+static int GetRtcpType(const talk_base::Buffer* packet) {
+  return (packet->length() >= kMinRtcpPacketLen) ?
+       static_cast<int>(packet->data()[1]) : 0;
+}
+
+BaseChannel::BaseChannel(talk_base::Thread* thread, MediaEngine* media_engine,
+                         MediaChannel* media_channel, BaseSession* session,
+                         const std::string& content_name,
+                         TransportChannel* transport_channel)
+    : worker_thread_(thread),
+      media_engine_(media_engine),
+      session_(session),
+      media_channel_(media_channel),
+      received_media_sink_(NULL),
+      sent_media_sink_(NULL),
+      content_name_(content_name),
+      transport_channel_(transport_channel),
+      rtcp_transport_channel_(NULL),
+      enabled_(false),
+      writable_(false),
+      has_codec_(false),
+      muted_(false) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  media_channel_->SetInterface(this);
+  transport_channel_->SignalWritableState.connect(
+      this, &BaseChannel::OnWritableState);
+  transport_channel_->SignalReadPacket.connect(
+      this, &BaseChannel::OnChannelRead);
+
+  LOG(LS_INFO) << "Created channel";
+
+  session->SignalState.connect(this, &BaseChannel::OnSessionState);
+}
+
+BaseChannel::~BaseChannel() {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  StopConnectionMonitor();
+  FlushRtcpMessages();  // Send any outstanding RTCP packets.
+  Clear();  // eats any outstanding messages or packets
+  // We must destroy the media channel before the transport channel, otherwise
+  // the media channel may try to send on the dead transport channel. NULLing
+  // is not an effective strategy since the sends will come on another thread.
+  delete media_channel_;
+  set_rtcp_transport_channel(NULL);
+  if (transport_channel_ != NULL)
+    session_->DestroyChannel(content_name_, transport_channel_->name());
+  LOG(LS_INFO) << "Destroyed channel";
+}
+
+bool BaseChannel::Enable(bool enable) {
+  // Can be called from thread other than worker thread
+  Send(enable ? MSG_ENABLE : MSG_DISABLE);
+  return true;
+}
+
+bool BaseChannel::Mute(bool mute) {
+  // Can be called from thread other than worker thread
+  Send(mute ? MSG_MUTE : MSG_UNMUTE);
+  return true;
+}
+
+bool BaseChannel::RemoveStream(uint32 ssrc) {
+  StreamMessageData data(ssrc, 0);
+  Send(MSG_REMOVESTREAM, &data);
+  return true;
+}
+
+bool BaseChannel::SetRtcpCName(const std::string& cname) {
+  SetRtcpCNameData data(cname);
+  Send(MSG_SETRTCPCNAME, &data);
+  return data.result;
+}
+
+bool BaseChannel::SetLocalContent(const MediaContentDescription* content,
+                                  ContentAction action) {
+  SetContentData data(content, action);
+  Send(MSG_SETLOCALCONTENT, &data);
+  return data.result;
+}
+
+bool BaseChannel::SetRemoteContent(const MediaContentDescription* content,
+                                   ContentAction action) {
+  SetContentData data(content, action);
+  Send(MSG_SETREMOTECONTENT, &data);
+  return data.result;
+}
+
+bool BaseChannel::SetMaxSendBandwidth(int max_bandwidth) {
+  SetBandwidthData data(max_bandwidth);
+  Send(MSG_SETMAXSENDBANDWIDTH, &data);
+  return data.result;
+}
+
+void BaseChannel::StartConnectionMonitor(int cms) {
+  socket_monitor_.reset(new SocketMonitor(transport_channel_,
+                                          worker_thread(),
+                                          talk_base::Thread::Current()));
+  socket_monitor_->SignalUpdate.connect(
+      this, &BaseChannel::OnConnectionMonitorUpdate);
+  socket_monitor_->Start(cms);
+}
+
+void BaseChannel::StopConnectionMonitor() {
+  if (socket_monitor_.get()) {
+    socket_monitor_->Stop();
+    socket_monitor_.reset();
+  }
+}
+
+void BaseChannel::set_rtcp_transport_channel(TransportChannel* channel) {
+  if (rtcp_transport_channel_ != channel) {
+    if (rtcp_transport_channel_) {
+      session_->DestroyChannel(content_name_, rtcp_transport_channel_->name());
+    }
+    rtcp_transport_channel_ = channel;
+    if (rtcp_transport_channel_) {
+      rtcp_transport_channel_->SignalWritableState.connect(
+          this, &BaseChannel::OnWritableState);
+      rtcp_transport_channel_->SignalReadPacket.connect(
+          this, &BaseChannel::OnChannelRead);
+    }
+  }
+}
+
+bool BaseChannel::SendPacket(talk_base::Buffer* packet) {
+  return SendPacket(false, packet);
+}
+
+bool BaseChannel::SendRtcp(talk_base::Buffer* packet) {
+  return SendPacket(true, packet);
+}
+
+int BaseChannel::SetOption(SocketType type, talk_base::Socket::Option opt,
+                           int value) {
+  switch (type) {
+    case ST_RTP: return transport_channel_->SetOption(opt, value);
+    case ST_RTCP: return rtcp_transport_channel_->SetOption(opt, value);
+    default: return -1;
+  }
+}
+
+void BaseChannel::OnWritableState(TransportChannel* channel) {
+  ASSERT(channel == transport_channel_ || channel == rtcp_transport_channel_);
+  if (transport_channel_->writable()
+      && (!rtcp_transport_channel_ || rtcp_transport_channel_->writable())) {
+    ChannelWritable_w();
+  } else {
+    ChannelNotWritable_w();
+  }
+}
+
+void BaseChannel::OnChannelRead(TransportChannel* channel,
+                                const char* data, size_t len) {
+  // OnChannelRead gets called from P2PSocket; now pass data to MediaEngine
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+  talk_base::Buffer packet(data, len);
+  // When using RTCP multiplexing we might get RTCP packets on the RTP
+  // transport. We feed RTP traffic into the demuxer to determine if it is RTCP.
+  bool rtcp = (channel == rtcp_transport_channel_ ||
+               rtcp_mux_filter_.DemuxRtcp(packet.data(), packet.length()));
+  HandlePacket(rtcp, &packet);
+}
+
+bool BaseChannel::SendPacket(bool rtcp, talk_base::Buffer* packet) {
+  // SendPacket gets called from MediaEngine, typically on an encoder thread.
+  // If the thread is not our worker thread, we will post to our worker
+  // so that the real work happens on our worker. This avoids us having to
+  // synchronize access to all the pieces of the send path, including
+  // SRTP and the inner workings of the transport channels.
+  // The only downside is that we can't return a proper failure code if
+  // needed. Since UDP is unreliable anyway, this should be a non-issue.
+  if (talk_base::Thread::Current() != worker_thread_) {
+    // Avoid a copy by transferring the ownership of the packet data.
+    int message_id = (!rtcp) ? MSG_RTPPACKET : MSG_RTCPPACKET;
+    PacketMessageData* data = new PacketMessageData;
+    packet->TransferTo(&data->packet);
+    worker_thread_->Post(this, message_id, data);
+    return true;
+  }
+
+  // Make sure we have a place to send this packet before doing anything.
+  // (We might get RTCP packets that we don't intend to send.)
+  // If we've negotiated RTCP mux, send RTCP over the RTP transport.
+  TransportChannel* channel = (!rtcp || rtcp_mux_filter_.IsActive()) ?
+      transport_channel_ : rtcp_transport_channel_;
+  if (!channel) {
+    return false;
+  }
+
+  // Protect ourselves against crazy data.
+  if (!ValidPacket(rtcp, packet)) {
+    LOG(LS_ERROR) << "Dropping outgoing " << content_name_ << " "
+                  << PacketType(rtcp) << " packet: wrong size="
+                  << packet->length();
+    return false;
+  }
+
+  // Push the packet down to the media sink.
+  // Need to do this before protecting the packet.
+  {
+    talk_base::CritScope cs(&sink_critical_section_);
+    if (sent_media_sink_) {
+      if (!rtcp) {
+        sent_media_sink_->OnRtpPacket(packet->data(), packet->length());
+      } else {
+        sent_media_sink_->OnRtcpPacket(packet->data(), packet->length());
+      }
+    }
+  }
+
+  // Protect if needed.
+  if (srtp_filter_.IsActive()) {
+    bool res;
+    char* data = packet->data();
+    int len = packet->length();
+    if (!rtcp) {
+      res = srtp_filter_.ProtectRtp(data, len, packet->capacity(), &len);
+      if (!res) {
+        LOG(LS_ERROR) << "Failed to protect " << content_name_
+                      << " RTP packet: size=" << len
+                      << ", seqnum=" << GetRtpSeqNum(packet)
+                      << ", SSRC=" << GetRtpSsrc(packet);
+        return false;
+      }
+    } else {
+      res = srtp_filter_.ProtectRtcp(data, len, packet->capacity(), &len);
+      if (!res) {
+        LOG(LS_ERROR) << "Failed to protect " << content_name_
+                     << " RTCP packet: size=" << len
+                      << ", type=" << GetRtcpType(packet);
+        return false;
+      }
+    }
+
+    // Update the length of the packet now that we've added the auth tag.
+    packet->SetLength(len);
+  }
+
+  // Bon voyage.
+  return (channel->SendPacket(packet->data(), packet->length())
+      == static_cast<int>(packet->length()));
+}
+
+void BaseChannel::HandlePacket(bool rtcp, talk_base::Buffer* packet) {
+  // Protect ourselvs against crazy data.
+  if (!ValidPacket(rtcp, packet)) {
+    LOG(LS_ERROR) << "Dropping incoming " << content_name_ << " "
+                  << PacketType(rtcp) << " packet: wrong size="
+                  << packet->length();
+    return;
+  }
+
+  // Unprotect the packet, if needed.
+  if (srtp_filter_.IsActive()) {
+    char* data = packet->data();
+    int len = packet->length();
+    bool res;
+    if (!rtcp) {
+      res = srtp_filter_.UnprotectRtp(data, len, &len);
+      if (!res) {
+        LOG(LS_ERROR) << "Failed to unprotect " << content_name_
+                      << " RTP packet: size=" << len
+                      << ", seqnum=" << GetRtpSeqNum(packet)
+                      << ", SSRC=" << GetRtpSsrc(packet);
+        return;
+      }
+    } else {
+      res = srtp_filter_.UnprotectRtcp(data, len, &len);
+      if (!res) {
+        LOG(LS_ERROR) << "Failed to unprotect " << content_name_
+                      << " RTCP packet: size=" << len
+                      << ", type=" << GetRtcpType(packet);
+        return;
+      }
+    }
+
+    packet->SetLength(len);
+  }
+
+  // Push it down to the media channel.
+  if (!rtcp) {
+    media_channel_->OnPacketReceived(packet);
+  } else {
+    media_channel_->OnRtcpReceived(packet);
+  }
+
+  // Push it down to the media sink.
+  {
+    talk_base::CritScope cs(&sink_critical_section_);
+    if (received_media_sink_) {
+      if (!rtcp) {
+        received_media_sink_->OnRtpPacket(packet->data(), packet->length());
+      } else {
+        received_media_sink_->OnRtcpPacket(packet->data(), packet->length());
+      }
+    }
+  }
+}
+
+void BaseChannel::OnSessionState(BaseSession* session,
+                                 BaseSession::State state) {
+  const MediaContentDescription* content = NULL;
+  switch (state) {
+    case Session::STATE_SENTINITIATE:
+      content = GetFirstContent(session->local_description());
+      if (content && !SetLocalContent(content, CA_OFFER)) {
+        LOG(LS_ERROR) << "Failure in SetLocalContent with CA_OFFER";
+        session->SetError(BaseSession::ERROR_CONTENT);
+      }
+      break;
+    case Session::STATE_SENTACCEPT:
+      content = GetFirstContent(session->local_description());
+      if (content && !SetLocalContent(content, CA_ANSWER)) {
+        LOG(LS_ERROR) << "Failure in SetLocalContent with CA_ANSWER";
+        session->SetError(BaseSession::ERROR_CONTENT);
+      }
+      break;
+    case Session::STATE_RECEIVEDINITIATE:
+      content = GetFirstContent(session->remote_description());
+      if (content && !SetRemoteContent(content, CA_OFFER)) {
+        LOG(LS_ERROR) << "Failure in SetRemoteContent with CA_OFFER";
+        session->SetError(BaseSession::ERROR_CONTENT);
+      }
+      break;
+    case Session::STATE_RECEIVEDACCEPT:
+      content = GetFirstContent(session->remote_description());
+      if (content && !SetRemoteContent(content, CA_ANSWER)) {
+        LOG(LS_ERROR) << "Failure in SetRemoteContent with CA_ANSWER";
+        session->SetError(BaseSession::ERROR_CONTENT);
+      }
+      break;
+    default:
+      break;
+  }
+}
+
+void BaseChannel::EnableMedia_w() {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  if (enabled_)
+    return;
+
+  LOG(LS_INFO) << "Channel enabled";
+  enabled_ = true;
+  ChangeState();
+}
+
+void BaseChannel::DisableMedia_w() {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  if (!enabled_)
+    return;
+
+  LOG(LS_INFO) << "Channel disabled";
+  enabled_ = false;
+  ChangeState();
+}
+
+void BaseChannel::MuteMedia_w() {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  if (muted_)
+    return;
+
+  if (media_channel()->Mute(true)) {
+    LOG(LS_INFO) << "Channel muted";
+    muted_ = true;
+  }
+}
+
+void BaseChannel::UnmuteMedia_w() {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  if (!muted_)
+    return;
+
+  if (media_channel()->Mute(false)) {
+    LOG(LS_INFO) << "Channel unmuted";
+    muted_ = false;
+  }
+}
+
+void BaseChannel::ChannelWritable_w() {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  if (writable_)
+    return;
+  LOG(LS_INFO) << "Channel socket writable ("
+               << transport_channel_->name().c_str() << ")";
+  writable_ = true;
+  ChangeState();
+}
+
+void BaseChannel::ChannelNotWritable_w() {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  if (!writable_)
+    return;
+
+  LOG(LS_INFO) << "Channel socket not writable ("
+               << transport_channel_->name().c_str() << ")";
+  writable_ = false;
+  ChangeState();
+}
+
+// Sets the maximum video bandwidth for automatic bandwidth adjustment.
+bool BaseChannel::SetMaxSendBandwidth_w(int max_bandwidth) {
+  return media_channel()->SetSendBandwidth(true, max_bandwidth);
+}
+
+bool BaseChannel::SetRtcpCName_w(const std::string& cname) {
+  return media_channel()->SetRtcpCName(cname);
+}
+
+bool BaseChannel::SetSrtp_w(const std::vector<CryptoParams>& cryptos,
+                            ContentAction action, ContentSource src) {
+  bool ret;
+  if (action == CA_OFFER) {
+    ret = srtp_filter_.SetOffer(cryptos, src);
+  } else if (action == CA_ANSWER) {
+    ret = srtp_filter_.SetAnswer(cryptos, src);
+  } else {
+    // CA_UPDATE, no crypto params.
+    ret = true;
+  }
+  return ret;
+}
+
+bool BaseChannel::SetRtcpMux_w(bool enable, ContentAction action,
+                               ContentSource src) {
+  bool ret;
+  if (action == CA_OFFER) {
+    ret = rtcp_mux_filter_.SetOffer(enable, src);
+  } else if (action == CA_ANSWER) {
+    ret = rtcp_mux_filter_.SetAnswer(enable, src);
+    if (ret && rtcp_mux_filter_.IsActive()) {
+      // We activated RTCP mux, close down the RTCP transport.
+      set_rtcp_transport_channel(NULL);
+      // If the RTP transport is already writable, then so are we.
+      if (transport_channel_->writable()) {
+        ChannelWritable_w();
+      }
+    }
+  } else {
+    // CA_UPDATE, no RTCP mux info.
+    ret = true;
+  }
+  return ret;
+}
+
+void BaseChannel::OnMessage(talk_base::Message *pmsg) {
+  switch (pmsg->message_id) {
+    case MSG_ENABLE:
+      EnableMedia_w();
+      break;
+    case MSG_DISABLE:
+      DisableMedia_w();
+      break;
+
+    case MSG_MUTE:
+      MuteMedia_w();
+      break;
+    case MSG_UNMUTE:
+      UnmuteMedia_w();
+      break;
+
+    case MSG_SETRTCPCNAME: {
+      SetRtcpCNameData* data = static_cast<SetRtcpCNameData*>(pmsg->pdata);
+      data->result = SetRtcpCName_w(data->cname);
+      break;
+    }
+
+    case MSG_SETLOCALCONTENT: {
+      SetContentData* data = static_cast<SetContentData*>(pmsg->pdata);
+      data->result = SetLocalContent_w(data->content, data->action);
+      break;
+    }
+    case MSG_SETREMOTECONTENT: {
+      SetContentData* data = static_cast<SetContentData*>(pmsg->pdata);
+      data->result = SetRemoteContent_w(data->content, data->action);
+      break;
+    }
+
+    case MSG_REMOVESTREAM: {
+      StreamMessageData* data = static_cast<StreamMessageData*>(pmsg->pdata);
+      RemoveStream_w(data->ssrc1);
+      break;
+    }
+
+    case MSG_SETMAXSENDBANDWIDTH: {
+      SetBandwidthData* data = static_cast<SetBandwidthData*>(pmsg->pdata);
+      data->result = SetMaxSendBandwidth_w(data->value);
+      break;
+    }
+
+    case MSG_RTPPACKET:
+    case MSG_RTCPPACKET: {
+      PacketMessageData* data = static_cast<PacketMessageData*>(pmsg->pdata);
+      SendPacket(pmsg->message_id == MSG_RTCPPACKET, &data->packet);
+      delete data;  // because it is Posted
+      break;
+    }
+  }
+}
+
+void BaseChannel::Send(uint32 id, talk_base::MessageData *pdata) {
+  worker_thread_->Send(this, id, pdata);
+}
+
+void BaseChannel::Post(uint32 id, talk_base::MessageData *pdata) {
+  worker_thread_->Post(this, id, pdata);
+}
+
+void BaseChannel::PostDelayed(int cmsDelay, uint32 id,
+                              talk_base::MessageData *pdata) {
+  worker_thread_->PostDelayed(cmsDelay, this, id, pdata);
+}
+
+void BaseChannel::Clear(uint32 id, talk_base::MessageList* removed) {
+  worker_thread_->Clear(this, id, removed);
+}
+
+void BaseChannel::FlushRtcpMessages() {
+  // Flush all remaining RTCP messages. This should only be called in
+  // destructor.
+  ASSERT(talk_base::Thread::Current() == worker_thread_);
+  talk_base::MessageList rtcp_messages;
+  Clear(MSG_RTCPPACKET, &rtcp_messages);
+  for (talk_base::MessageList::iterator it = rtcp_messages.begin();
+       it != rtcp_messages.end(); ++it) {
+    Send(MSG_RTCPPACKET, it->pdata);
+  }
+}
+
+VoiceChannel::VoiceChannel(talk_base::Thread* thread,
+                           MediaEngine* media_engine,
+                           VoiceMediaChannel* media_channel,
+                           BaseSession* session,
+                           const std::string& content_name,
+                           bool rtcp)
+    : BaseChannel(thread, media_engine, media_channel, session, content_name,
+                  session->CreateChannel(content_name, "rtp")),
+      received_media_(false) {
+  if (rtcp) {
+    set_rtcp_transport_channel(session->CreateChannel(content_name, "rtcp"));
+  }
+  // Can't go in BaseChannel because certain session states will
+  // trigger pure virtual functions, such as GetFirstContent().
+  OnSessionState(session, session->state());
+
+  media_channel->SignalMediaError.connect(
+      this, &VoiceChannel::OnVoiceChannelError);
+}
+
+VoiceChannel::~VoiceChannel() {
+  StopAudioMonitor();
+  StopMediaMonitor();
+  // this can't be done in the base class, since it calls a virtual
+  DisableMedia_w();
+}
+
+bool VoiceChannel::AddStream(uint32 ssrc) {
+  StreamMessageData data(ssrc, 0);
+  Send(MSG_ADDSTREAM, &data);
+  return true;
+}
+
+bool VoiceChannel::SetRingbackTone(const void* buf, int len) {
+  SetRingbackToneMessageData data(buf, len);
+  Send(MSG_SETRINGBACKTONE, &data);
+  return true;
+}
+
+// TODO: Handle early media the right way. We should get an explicit
+// ringing message telling us to start playing local ringback, which we cancel
+// if any early media actually arrives. For now, we do the opposite, which is
+// to wait 1 second for early media, and start playing local ringback if none
+// arrives.
+void VoiceChannel::SetEarlyMedia(bool enable) {
+  if (enable) {
+    // Start the early media timeout
+    PostDelayed(kEarlyMediaTimeout, MSG_EARLYMEDIATIMEOUT);
+  } else {
+    // Stop the timeout if currently going.
+    Clear(MSG_EARLYMEDIATIMEOUT);
+  }
+}
+
+bool VoiceChannel::PlayRingbackTone(bool play, bool loop) {
+  PlayRingbackToneMessageData data(play, loop);
+  Send(MSG_PLAYRINGBACKTONE, &data);
+  return data.result;
+}
+
+bool VoiceChannel::PressDTMF(int digit, bool playout) {
+  DtmfMessageData data(digit, playout);
+  Send(MSG_PRESSDTMF, &data);
+  return data.result;
+}
+
+void VoiceChannel::StartMediaMonitor(int cms) {
+  media_monitor_.reset(new VoiceMediaMonitor(media_channel(), worker_thread(),
+      talk_base::Thread::Current()));
+  media_monitor_->SignalUpdate.connect(
+      this, &VoiceChannel::OnMediaMonitorUpdate);
+  media_monitor_->Start(cms);
+}
+
+void VoiceChannel::StopMediaMonitor() {
+  if (media_monitor_.get()) {
+    media_monitor_->Stop();
+    media_monitor_->SignalUpdate.disconnect(this);
+    media_monitor_.reset();
+  }
+}
+
+void VoiceChannel::StartAudioMonitor(int cms) {
+  audio_monitor_.reset(new AudioMonitor(this, talk_base::Thread::Current()));
+  audio_monitor_
+    ->SignalUpdate.connect(this, &VoiceChannel::OnAudioMonitorUpdate);
+  audio_monitor_->Start(cms);
+}
+
+void VoiceChannel::StopAudioMonitor() {
+  if (audio_monitor_.get()) {
+    audio_monitor_->Stop();
+    audio_monitor_.reset();
+  }
+}
+
+int VoiceChannel::GetInputLevel_w() {
+  return media_engine()->GetInputLevel();
+}
+
+int VoiceChannel::GetOutputLevel_w() {
+  return media_channel()->GetOutputLevel();
+}
+
+void VoiceChannel::GetActiveStreams_w(AudioInfo::StreamList* actives) {
+  media_channel()->GetActiveStreams(actives);
+}
+
+void VoiceChannel::OnChannelRead(TransportChannel* channel,
+                                 const char* data, size_t len) {
+  BaseChannel::OnChannelRead(channel, data, len);
+
+  // Set a flag when we've received an RTP packet. If we're waiting for early
+  // media, this will disable the timeout.
+  // If we were playing out our local ringback, make sure it is stopped to
+  // prevent it from interfering with the incoming media.
+  if (!received_media_) {
+    if (!PlayRingbackTone_w(false, false)) {
+      LOG(LS_ERROR) << "Failed to stop ringback tone.";
+      SendLastMediaError();
+    }
+  }
+}
+
+void VoiceChannel::ChangeState() {
+  // render incoming data if we are the active call
+  // we receive data on the default channel and multiplexed streams
+  bool recv = enabled();
+  if (!media_channel()->SetPlayout(recv)) {
+    SendLastMediaError();
+  }
+
+  // send outgoing data if we are the active call, have the
+  // remote party's codec, and have a writable transport
+  // we only send data on the default channel
+  bool send = enabled() && has_codec() && writable();
+  SendFlags send_flag = send ? SEND_MICROPHONE : SEND_NOTHING;
+  if (!media_channel()->SetSend(send_flag)) {
+    LOG(LS_ERROR) << "Failed to SetSend " << send_flag << " on voice channel";
+    SendLastMediaError();
+  }
+
+  LOG(LS_INFO) << "Changing voice state, recv=" << recv << " send=" << send;
+}
+
+const MediaContentDescription* VoiceChannel::GetFirstContent(
+    const SessionDescription* sdesc) {
+  const ContentInfo* cinfo = GetFirstAudioContent(sdesc);
+  if (cinfo == NULL)
+    return NULL;
+
+  return static_cast<const MediaContentDescription*>(cinfo->description);
+}
+
+bool VoiceChannel::SetLocalContent_w(const MediaContentDescription* content,
+                                     ContentAction action) {
+  ASSERT(worker_thread() == talk_base::Thread::Current());
+  LOG(LS_INFO) << "Setting local voice description";
+
+  const AudioContentDescription* audio =
+      static_cast<const AudioContentDescription*>(content);
+  ASSERT(audio != NULL);
+
+  bool ret;
+  // set SRTP
+  ret = SetSrtp_w(audio->cryptos(), action, CS_LOCAL);
+  // set RTCP mux
+  if (ret) {
+    ret = SetRtcpMux_w(audio->rtcp_mux(), action, CS_LOCAL);
+  }
+  // set payload type and config for voice codecs
+  if (ret) {
+    ret = media_channel()->SetRecvCodecs(audio->codecs());
+  }
+  return ret;
+}
+
+bool VoiceChannel::SetRemoteContent_w(const MediaContentDescription* content,
+                                      ContentAction action) {
+  ASSERT(worker_thread() == talk_base::Thread::Current());
+  LOG(LS_INFO) << "Setting remote voice description";
+
+  const AudioContentDescription* audio =
+      static_cast<const AudioContentDescription*>(content);
+  ASSERT(audio != NULL);
+
+  bool ret;
+  // set the sending SSRC, if the remote side gave us one
+  if (audio->ssrc_set()) {
+    media_channel()->SetSendSsrc(audio->ssrc());
+  }
+  // set SRTP
+  ret = SetSrtp_w(audio->cryptos(), action, CS_REMOTE);
+  // set RTCP mux
+  if (ret) {
+    ret = SetRtcpMux_w(audio->rtcp_mux(), action, CS_REMOTE);
+  }
+  // set codecs and payload types
+  if (ret) {
+    ret = media_channel()->SetSendCodecs(audio->codecs());
+  }
+
+  int audio_options = 0;
+  if (audio->conference_mode()) {
+    audio_options |= OPT_CONFERENCE;
+  }
+  if (!media_channel()->SetOptions(audio_options)) {
+    // Log an error on failure, but don't abort the call.
+    LOG(LS_ERROR) << "Failed to set voice channel options";
+  }
+
+  // update state
+  if (ret) {
+    set_has_codec(true);
+    ChangeState();
+  }
+  return ret;
+}
+
+void VoiceChannel::AddStream_w(uint32 ssrc) {
+  ASSERT(worker_thread() == talk_base::Thread::Current());
+  media_channel()->AddStream(ssrc);
+}
+
+void VoiceChannel::RemoveStream_w(uint32 ssrc) {
+  media_channel()->RemoveStream(ssrc);
+}
+
+void VoiceChannel::SetRingbackTone_w(const void* buf, int len) {
+  ASSERT(worker_thread() == talk_base::Thread::Current());
+  media_channel()->SetRingbackTone(static_cast<const char*>(buf), len);
+}
+
+bool VoiceChannel::PlayRingbackTone_w(bool play, bool loop) {
+  ASSERT(worker_thread() == talk_base::Thread::Current());
+  if (play) {
+    LOG(LS_INFO) << "Playing ringback tone, loop=" << loop;
+  } else {
+    LOG(LS_INFO) << "Stopping ringback tone";
+  }
+  return media_channel()->PlayRingbackTone(play, loop);
+}
+
+void VoiceChannel::HandleEarlyMediaTimeout() {
+  // This occurs on the main thread, not the worker thread.
+  if (!received_media_) {
+    LOG(LS_INFO) << "No early media received before timeout";
+    SignalEarlyMediaTimeout(this);
+  }
+}
+
+bool VoiceChannel::PressDTMF_w(int digit, bool playout) {
+  if (!enabled() || !writable()) {
+    return false;
+  }
+
+  return media_channel()->PressDTMF(digit, playout);
+}
+
+void VoiceChannel::OnMessage(talk_base::Message *pmsg) {
+  switch (pmsg->message_id) {
+    case MSG_ADDSTREAM: {
+      StreamMessageData* data = static_cast<StreamMessageData*>(pmsg->pdata);
+      AddStream_w(data->ssrc1);
+      break;
+    }
+    case MSG_SETRINGBACKTONE: {
+      SetRingbackToneMessageData* data =
+          static_cast<SetRingbackToneMessageData*>(pmsg->pdata);
+      SetRingbackTone_w(data->buf, data->len);
+      break;
+    }
+    case MSG_PLAYRINGBACKTONE: {
+      PlayRingbackToneMessageData* data =
+          static_cast<PlayRingbackToneMessageData*>(pmsg->pdata);
+      data->result = PlayRingbackTone_w(data->play, data->loop);
+      break;
+    }
+    case MSG_EARLYMEDIATIMEOUT:
+      HandleEarlyMediaTimeout();
+      break;
+    case MSG_PRESSDTMF: {
+      DtmfMessageData* data = static_cast<DtmfMessageData*>(pmsg->pdata);
+      data->result = PressDTMF_w(data->digit, data->playout);
+      break;
+    }
+    case MSG_CHANNEL_ERROR: {
+      VoiceChannelErrorMessageData* data =
+          static_cast<VoiceChannelErrorMessageData*>(pmsg->pdata);
+      SignalMediaError(this, data->ssrc, data->error);
+      delete data;
+      break;
+    }
+
+    default:
+      BaseChannel::OnMessage(pmsg);
+      break;
+  }
+}
+
+void VoiceChannel::OnConnectionMonitorUpdate(
+    SocketMonitor* monitor, const std::vector<ConnectionInfo>& infos) {
+  SignalConnectionMonitor(this, infos);
+}
+
+void VoiceChannel::OnMediaMonitorUpdate(
+    VoiceMediaChannel* media_channel, const VoiceMediaInfo& info) {
+  ASSERT(media_channel == this->media_channel());
+  SignalMediaMonitor(this, info);
+}
+
+void VoiceChannel::OnAudioMonitorUpdate(AudioMonitor* monitor,
+                                        const AudioInfo& info) {
+  SignalAudioMonitor(this, info);
+}
+
+void VoiceChannel::OnVoiceChannelError(
+    uint32 ssrc, VoiceMediaChannel::Error error) {
+  VoiceChannelErrorMessageData *data = new VoiceChannelErrorMessageData(
+      ssrc, error);
+  signaling_thread()->Post(this, MSG_CHANNEL_ERROR, data);
+}
+
+VideoChannel::VideoChannel(talk_base::Thread* thread,
+                           MediaEngine* media_engine,
+                           VideoMediaChannel* media_channel,
+                           BaseSession* session,
+                           const std::string& content_name,
+                           bool rtcp,
+                           VoiceChannel* voice_channel)
+    : BaseChannel(thread, media_engine, media_channel, session, content_name,
+                  session->CreateChannel(content_name, "video_rtp")),
+      voice_channel_(voice_channel), renderer_(NULL) {
+  if (rtcp) {
+    set_rtcp_transport_channel(
+        session->CreateChannel(content_name, "video_rtcp"));
+  }
+  // Can't go in BaseChannel because certain session states will
+  // trigger pure virtual functions, such as GetFirstContent()
+  OnSessionState(session, session->state());
+
+  media_channel->SignalMediaError.connect(
+      this, &VideoChannel::OnVideoChannelError);
+}
+
+void VoiceChannel::SendLastMediaError() {
+  uint32 ssrc;
+  VoiceMediaChannel::Error error;
+  media_channel()->GetLastMediaError(&ssrc, &error);
+  SignalMediaError(this, ssrc, error);
+}
+
+VideoChannel::~VideoChannel() {
+  StopMediaMonitor();
+  // this can't be done in the base class, since it calls a virtual
+  DisableMedia_w();
+}
+
+bool VideoChannel::AddStream(uint32 ssrc, uint32 voice_ssrc) {
+  StreamMessageData data(ssrc, voice_ssrc);
+  Send(MSG_ADDSTREAM, &data);
+  return true;
+}
+
+bool VideoChannel::SetRenderer(uint32 ssrc, VideoRenderer* renderer) {
+  RenderMessageData data(ssrc, renderer);
+  Send(MSG_SETRENDERER, &data);
+  return true;
+}
+
+
+
+bool VideoChannel::SendIntraFrame() {
+  Send(MSG_SENDINTRAFRAME);
+  return true;
+}
+bool VideoChannel::RequestIntraFrame() {
+  Send(MSG_REQUESTINTRAFRAME);
+  return true;
+}
+
+void VideoChannel::ChangeState() {
+  // render incoming data if we are the active call
+  // we receive data on the default channel and multiplexed streams
+  bool recv = enabled();
+  if (!media_channel()->SetRender(recv)) {
+    LOG(LS_ERROR) << "Failed to SetRender on video channel";
+    // TODO: Report error back to server.
+  }
+
+  // send outgoing data if we are the active call, have the
+  // remote party's codec, and have a writable transport
+  // we only send data on the default channel
+  bool send = enabled() && has_codec() && writable();
+  if (!media_channel()->SetSend(send)) {
+    LOG(LS_ERROR) << "Failed to SetSend on video channel";
+    // TODO: Report error back to server.
+  }
+
+  LOG(LS_INFO) << "Changing video state, recv=" << recv << " send=" << send;
+}
+
+void VideoChannel::StartMediaMonitor(int cms) {
+  media_monitor_.reset(new VideoMediaMonitor(media_channel(), worker_thread(),
+      talk_base::Thread::Current()));
+  media_monitor_->SignalUpdate.connect(
+      this, &VideoChannel::OnMediaMonitorUpdate);
+  media_monitor_->Start(cms);
+}
+
+void VideoChannel::StopMediaMonitor() {
+  if (media_monitor_.get()) {
+    media_monitor_->Stop();
+    media_monitor_.reset();
+  }
+}
+
+const MediaContentDescription* VideoChannel::GetFirstContent(
+    const SessionDescription* sdesc) {
+  const ContentInfo* cinfo = GetFirstVideoContent(sdesc);
+  if (cinfo == NULL)
+    return NULL;
+
+  return static_cast<const MediaContentDescription*>(cinfo->description);
+}
+
+bool VideoChannel::SetLocalContent_w(const MediaContentDescription* content,
+                                     ContentAction action) {
+  ASSERT(worker_thread() == talk_base::Thread::Current());
+  LOG(LS_INFO) << "Setting local video description";
+
+  const VideoContentDescription* video =
+      static_cast<const VideoContentDescription*>(content);
+  ASSERT(video != NULL);
+
+  bool ret;
+  // set SRTP
+  ret = SetSrtp_w(video->cryptos(), action, CS_LOCAL);
+  // set RTCP mux
+  if (ret) {
+    ret = SetRtcpMux_w(video->rtcp_mux(), action, CS_LOCAL);
+  }
+  // set payload types and config for receiving video
+  if (ret) {
+    ret = media_channel()->SetRecvCodecs(video->codecs());
+  }
+  return ret;
+}
+
+bool VideoChannel::SetRemoteContent_w(const MediaContentDescription* content,
+                                      ContentAction action) {
+  ASSERT(worker_thread() == talk_base::Thread::Current());
+  LOG(LS_INFO) << "Setting remote video description";
+
+  const VideoContentDescription* video =
+      static_cast<const VideoContentDescription*>(content);
+  ASSERT(video != NULL);
+
+  bool ret;
+  // set the sending SSRC, if the remote side gave us one
+  // TODO: remove this, since it's not needed.
+  if (video->ssrc_set()) {
+    media_channel()->SetSendSsrc(video->ssrc());
+  }
+  // set SRTP
+  ret = SetSrtp_w(video->cryptos(), action, CS_REMOTE);
+  // set RTCP mux
+  if (ret) {
+    ret = SetRtcpMux_w(video->rtcp_mux(), action, CS_REMOTE);
+  }
+  // Set video bandwidth parameters.
+  if (ret) {
+    int bandwidth_bps = video->bandwidth();
+    bool auto_bandwidth = (bandwidth_bps == kAutoBandwidth);
+    ret = media_channel()->SetSendBandwidth(auto_bandwidth, bandwidth_bps);
+  }
+  if (ret) {
+    ret = media_channel()->SetSendCodecs(video->codecs());
+  }
+  media_channel()->SetRtpExtensionHeaders(!video->rtp_headers_disabled());
+  if (ret) {
+    set_has_codec(true);
+    ChangeState();
+  }
+  return ret;
+}
+
+void VideoChannel::AddStream_w(uint32 ssrc, uint32 voice_ssrc) {
+  media_channel()->AddStream(ssrc, voice_ssrc);
+}
+
+void VideoChannel::RemoveStream_w(uint32 ssrc) {
+  media_channel()->RemoveStream(ssrc);
+}
+
+void VideoChannel::SetRenderer_w(uint32 ssrc, VideoRenderer* renderer) {
+  media_channel()->SetRenderer(ssrc, renderer);
+}
+
+
+void VideoChannel::OnMessage(talk_base::Message *pmsg) {
+  switch (pmsg->message_id) {
+    case MSG_ADDSTREAM: {
+      StreamMessageData* data = static_cast<StreamMessageData*>(pmsg->pdata);
+      AddStream_w(data->ssrc1, data->ssrc2);
+      break;
+    }
+    case MSG_SETRENDERER: {
+      RenderMessageData* data = static_cast<RenderMessageData*>(pmsg->pdata);
+      SetRenderer_w(data->ssrc, data->renderer);
+      break;
+    }
+    case MSG_SENDINTRAFRAME:
+      SendIntraFrame_w();
+      break;
+    case MSG_REQUESTINTRAFRAME:
+      RequestIntraFrame_w();
+      break;
+    case MSG_CHANNEL_ERROR: {
+      const VideoChannelErrorMessageData* data =
+          static_cast<VideoChannelErrorMessageData*>(pmsg->pdata);
+      SignalMediaError(this, data->ssrc, data->error);
+      delete data;
+      break;
+    }
+    default:
+      BaseChannel::OnMessage(pmsg);
+      break;
+  }
+}
+
+void VideoChannel::OnConnectionMonitorUpdate(
+    SocketMonitor *monitor, const std::vector<ConnectionInfo> &infos) {
+  SignalConnectionMonitor(this, infos);
+}
+
+void VideoChannel::OnMediaMonitorUpdate(
+    VideoMediaChannel* media_channel, const VideoMediaInfo &info) {
+  ASSERT(media_channel == this->media_channel());
+  SignalMediaMonitor(this, info);
+}
+
+
+void VideoChannel::OnVideoChannelError(uint32 ssrc,
+                                       VideoMediaChannel::Error error) {
+  VideoChannelErrorMessageData* data = new VideoChannelErrorMessageData(
+      ssrc, error);
+  signaling_thread()->Post(this, MSG_CHANNEL_ERROR, data);
+}
+
+}  // namespace cricket
diff --git a/talk/session/phone/channel.h b/talk/session/phone/channel.h
new file mode 100644
index 0000000..e7bee83
--- /dev/null
+++ b/talk/session/phone/channel.h
@@ -0,0 +1,441 @@
+/*
+ * 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.
+ */
+
+#ifndef TALK_SESSION_PHONE_CHANNEL_H_
+#define TALK_SESSION_PHONE_CHANNEL_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/asyncudpsocket.h"
+#include "talk/base/criticalsection.h"
+#include "talk/base/network.h"
+#include "talk/base/sigslot.h"
+#include "talk/p2p/client/socketmonitor.h"
+#include "talk/p2p/base/session.h"
+#include "talk/session/phone/audiomonitor.h"
+#include "talk/session/phone/mediaengine.h"
+#include "talk/session/phone/mediachannel.h"
+#include "talk/session/phone/mediamonitor.h"
+#include "talk/session/phone/rtcpmuxfilter.h"
+#include "talk/session/phone/srtpfilter.h"
+
+namespace cricket {
+
+class MediaContentDescription;
+class MediaSinkInterface;
+struct CryptoParams;
+
+enum {
+  MSG_ENABLE = 1,
+  MSG_DISABLE = 2,
+  MSG_MUTE = 3,
+  MSG_UNMUTE = 4,
+  MSG_SETREMOTECONTENT = 5,
+  MSG_SETLOCALCONTENT = 6,
+  MSG_EARLYMEDIATIMEOUT = 8,
+  MSG_PRESSDTMF = 9,
+  MSG_SETRENDERER = 10,
+  MSG_ADDSTREAM = 11,
+  MSG_REMOVESTREAM = 12,
+  MSG_SETRINGBACKTONE = 13,
+  MSG_PLAYRINGBACKTONE = 14,
+  MSG_SETMAXSENDBANDWIDTH = 15,
+  MSG_SETRTCPCNAME = 18,
+  MSG_SENDINTRAFRAME = 19,
+  MSG_REQUESTINTRAFRAME = 20,
+  MSG_RTPPACKET = 22,
+  MSG_RTCPPACKET = 23,
+  MSG_CHANNEL_ERROR = 24
+};
+
+// BaseChannel contains logic common to voice and video, including
+// enable/mute, marshaling calls to a worker thread, and
+// connection and media monitors.
+class BaseChannel
+    : public talk_base::MessageHandler, public sigslot::has_slots<>,
+      public MediaChannel::NetworkInterface {
+ public:
+  BaseChannel(talk_base::Thread* thread, MediaEngine* media_engine,
+              MediaChannel* channel, BaseSession* session,
+              const std::string& content_name,
+              TransportChannel* transport_channel);
+  virtual ~BaseChannel();
+
+  talk_base::Thread* worker_thread() const { return worker_thread_; }
+  BaseSession* session() const { return session_; }
+  const std::string& content_name() { return content_name_; }
+  TransportChannel* transport_channel() const {
+    return transport_channel_;
+  }
+  TransportChannel* rtcp_transport_channel() const {
+    return rtcp_transport_channel_;
+  }
+  bool enabled() const { return enabled_; }
+  bool secure() const { return srtp_filter_.IsActive(); }
+
+  // Channel control
+  bool SetRtcpCName(const std::string& cname);
+  bool SetLocalContent(const MediaContentDescription* content,
+                       ContentAction action);
+  bool SetRemoteContent(const MediaContentDescription* content,
+                        ContentAction action);
+  bool SetMaxSendBandwidth(int max_bandwidth);
+
+  bool Enable(bool enable);
+  bool Mute(bool mute);
+
+  // Multiplexing
+  bool RemoveStream(uint32 ssrc);
+
+  // Monitoring
+  void StartConnectionMonitor(int cms);
+  void StopConnectionMonitor();
+
+  // Set and get media sinks for recording media.
+  void set_received_media_sink(MediaSinkInterface* sink) {
+    talk_base::CritScope cs(&sink_critical_section_);
+    received_media_sink_ = sink;
+  }
+  const MediaSinkInterface* received_media_sink() {
+    talk_base::CritScope cs(&sink_critical_section_);
+    return received_media_sink_;
+  }
+  void set_sent_media_sink(MediaSinkInterface* sink) {
+    talk_base::CritScope cs(&sink_critical_section_);
+    sent_media_sink_ = sink;
+  }
+  const MediaSinkInterface* sent_media_sink() {
+    talk_base::CritScope cs(&sink_critical_section_);
+    return sent_media_sink_;
+  }
+
+ protected:
+  MediaEngine* media_engine() const { return media_engine_; }
+  virtual MediaChannel* media_channel() const { return media_channel_; }
+  void set_rtcp_transport_channel(TransportChannel* transport);
+  bool writable() const { return writable_; }
+  bool has_codec() const { return has_codec_; }
+  void set_has_codec(bool has_codec) { has_codec_ = has_codec; }
+  bool muted() const { return muted_; }
+  talk_base::Thread* signaling_thread() { return session_->signaling_thread(); }
+
+  void Send(uint32 id, talk_base::MessageData *pdata = NULL);
+  void Post(uint32 id, talk_base::MessageData *pdata = NULL);
+  void PostDelayed(int cmsDelay, uint32 id = 0,
+                   talk_base::MessageData *pdata = NULL);
+  void Clear(uint32 id = talk_base::MQID_ANY,
+             talk_base::MessageList* removed = NULL);
+  void FlushRtcpMessages();
+
+  // NetworkInterface implementation, called by MediaEngine
+  virtual bool SendPacket(talk_base::Buffer* packet);
+  virtual bool SendRtcp(talk_base::Buffer* packet);
+  virtual int SetOption(SocketType type, talk_base::Socket::Option o, int val);
+
+  // From TransportChannel
+  void OnWritableState(TransportChannel* channel);
+  void OnChannelRead(TransportChannel* channel, const char *data, size_t len);
+
+  bool SendPacket(bool rtcp, talk_base::Buffer* packet);
+  void HandlePacket(bool rtcp, talk_base::Buffer* packet);
+
+  // Setting the send codec based on the remote description.
+  void OnSessionState(BaseSession* session, BaseSession::State state);
+
+  void EnableMedia_w();
+  void DisableMedia_w();
+  void MuteMedia_w();
+  void UnmuteMedia_w();
+  void ChannelWritable_w();
+  void ChannelNotWritable_w();
+
+  struct StreamMessageData : public talk_base::MessageData {
+    StreamMessageData(uint32 s1, uint32 s2) : ssrc1(s1), ssrc2(s2) {}
+    uint32 ssrc1;
+    uint32 ssrc2;
+  };
+  virtual void RemoveStream_w(uint32 ssrc) = 0;
+
+  virtual void ChangeState() = 0;
+
+  struct SetRtcpCNameData : public talk_base::MessageData {
+    explicit SetRtcpCNameData(const std::string& cname)
+        : cname(cname), result(false) {}
+    std::string cname;
+    bool result;
+  };
+  bool SetRtcpCName_w(const std::string& cname);
+
+  struct SetContentData : public talk_base::MessageData {
+    SetContentData(const MediaContentDescription* content,
+                   ContentAction action)
+        : content(content), action(action), result(false) {}
+    const MediaContentDescription* content;
+    ContentAction action;
+    bool result;
+  };
+
+  // Gets the content appropriate to the channel (audio or video).
+  virtual const MediaContentDescription* GetFirstContent(
+      const SessionDescription* sdesc) = 0;
+  virtual bool SetLocalContent_w(const MediaContentDescription* content,
+                                 ContentAction action) = 0;
+  virtual bool SetRemoteContent_w(const MediaContentDescription* content,
+                                  ContentAction action) = 0;
+
+  bool SetSrtp_w(const std::vector<CryptoParams>& params, ContentAction action,
+                 ContentSource src);
+  bool SetRtcpMux_w(bool enable, ContentAction action, ContentSource src);
+
+  struct SetBandwidthData : public talk_base::MessageData {
+    explicit SetBandwidthData(int value) : value(value), result(false) {}
+    int value;
+    bool result;
+  };
+  bool SetMaxSendBandwidth_w(int max_bandwidth);
+
+  // From MessageHandler
+  virtual void OnMessage(talk_base::Message *pmsg);
+
+  // Handled in derived classes
+  virtual void OnConnectionMonitorUpdate(SocketMonitor *monitor,
+      const std::vector<ConnectionInfo> &infos) = 0;
+
+ private:
+  talk_base::Thread *worker_thread_;
+  MediaEngine *media_engine_;
+  BaseSession *session_;
+  MediaChannel *media_channel_;
+  // Media sinks to handle the received or sent RTP/RTCP packets. These are
+  // reference to the objects owned by the media recorder.
+  MediaSinkInterface* received_media_sink_;
+  MediaSinkInterface* sent_media_sink_;
+  talk_base::CriticalSection sink_critical_section_;
+
+  std::string content_name_;
+  TransportChannel *transport_channel_;
+  TransportChannel *rtcp_transport_channel_;
+  SrtpFilter srtp_filter_;
+  RtcpMuxFilter rtcp_mux_filter_;
+  talk_base::scoped_ptr<SocketMonitor> socket_monitor_;
+  bool enabled_;
+  bool writable_;
+  bool has_codec_;
+  bool muted_;
+};
+
+// VoiceChannel is a specialization that adds support for early media, DTMF,
+// and input/output level monitoring.
+class VoiceChannel : public BaseChannel {
+ public:
+  VoiceChannel(talk_base::Thread *thread, MediaEngine *media_engine,
+               VoiceMediaChannel *channel, BaseSession *session,
+               const std::string& content_name, bool rtcp);
+  ~VoiceChannel();
+
+  // downcasts a MediaChannel
+  virtual VoiceMediaChannel* media_channel() const {
+    return static_cast<VoiceMediaChannel*>(BaseChannel::media_channel());
+  }
+
+  // Add an incoming stream with the specified SSRC.
+  bool AddStream(uint32 ssrc);
+
+  bool SetRingbackTone(const void* buf, int len);
+  void SetEarlyMedia(bool enable);
+  // This signal is emitted when we have gone a period of time without
+  // receiving early media. When received, a UI should start playing its
+  // own ringing sound
+  sigslot::signal1<VoiceChannel*> SignalEarlyMediaTimeout;
+
+  bool PlayRingbackTone(bool play, bool loop);
+  bool PressDTMF(int digit, bool playout);
+
+  // Monitoring functions
+  sigslot::signal2<VoiceChannel*, const std::vector<ConnectionInfo> &>
+      SignalConnectionMonitor;
+
+  void StartMediaMonitor(int cms);
+  void StopMediaMonitor();
+  sigslot::signal2<VoiceChannel*, const VoiceMediaInfo&> SignalMediaMonitor;
+
+  void StartAudioMonitor(int cms);
+  void StopAudioMonitor();
+  sigslot::signal2<VoiceChannel*, const AudioInfo&> SignalAudioMonitor;
+
+  int GetInputLevel_w();
+  int GetOutputLevel_w();
+  void GetActiveStreams_w(AudioInfo::StreamList* actives);
+
+  // Signal errors from VoiceMediaChannel.  Arguments are:
+  //     ssrc(uint32), and error(VoiceMediaChannel::Error).
+  sigslot::signal3<VoiceChannel*, uint32, VoiceMediaChannel::Error>
+      SignalMediaError;
+
+ private:
+  struct SetRingbackToneMessageData : public talk_base::MessageData {
+    SetRingbackToneMessageData(const void* b, int l)
+        : buf(b),
+          len(l) {
+    }
+    const void* buf;
+    int len;
+  };
+  struct PlayRingbackToneMessageData : public talk_base::MessageData {
+    PlayRingbackToneMessageData(bool p, bool l)
+        : play(p),
+          loop(l),
+          result(false) {
+    }
+    bool play;
+    bool loop;
+    bool result;
+  };
+  struct DtmfMessageData : public talk_base::MessageData {
+    DtmfMessageData(int d, bool p)
+        : digit(d),
+          playout(p),
+          result(false) {
+    }
+    int digit;
+    bool playout;
+    bool result;
+  };
+
+  // overrides from BaseChannel
+  virtual void OnChannelRead(TransportChannel* channel,
+                             const char *data, size_t len);
+  virtual void ChangeState();
+  virtual const MediaContentDescription* GetFirstContent(
+      const SessionDescription* sdesc);
+  virtual bool SetLocalContent_w(const MediaContentDescription* content,
+                                 ContentAction action);
+  virtual bool SetRemoteContent_w(const MediaContentDescription* content,
+                                  ContentAction action);
+
+  void AddStream_w(uint32 ssrc);
+  void RemoveStream_w(uint32 ssrc);
+
+  void SetRingbackTone_w(const void* buf, int len);
+  bool PlayRingbackTone_w(bool play, bool loop);
+  void HandleEarlyMediaTimeout();
+  bool PressDTMF_w(int digit, bool playout);
+
+  virtual void OnMessage(talk_base::Message *pmsg);
+  virtual void OnConnectionMonitorUpdate(
+      SocketMonitor *monitor, const std::vector<ConnectionInfo> &infos);
+  virtual void OnMediaMonitorUpdate(
+      VoiceMediaChannel *media_channel, const VoiceMediaInfo& info);
+  void OnAudioMonitorUpdate(AudioMonitor *monitor, const AudioInfo& info);
+  void OnVoiceChannelError(uint32 ssrc, VoiceMediaChannel::Error error);
+  void SendLastMediaError();
+
+  static const int kEarlyMediaTimeout = 1000;
+  bool received_media_;
+  talk_base::scoped_ptr<VoiceMediaMonitor> media_monitor_;
+  talk_base::scoped_ptr<AudioMonitor> audio_monitor_;
+};
+
+// VideoChannel is a specialization for video.
+class VideoChannel : public BaseChannel {
+ public:
+  VideoChannel(talk_base::Thread *thread, MediaEngine *media_engine,
+               VideoMediaChannel *channel, BaseSession *session,
+               const std::string& content_name, bool rtcp,
+               VoiceChannel *voice_channel);
+  ~VideoChannel();
+
+  // downcasts a MediaChannel
+  virtual VideoMediaChannel* media_channel() const {
+    return static_cast<VideoMediaChannel*>(BaseChannel::media_channel());
+  }
+
+  // Add an incoming stream with the specified SSRC.
+  bool AddStream(uint32 ssrc, uint32 voice_ssrc);
+
+  bool SetRenderer(uint32 ssrc, VideoRenderer* renderer);
+
+
+  sigslot::signal2<VideoChannel*, const std::vector<ConnectionInfo> &>
+      SignalConnectionMonitor;
+
+  void StartMediaMonitor(int cms);
+  void StopMediaMonitor();
+  sigslot::signal2<VideoChannel*, const VideoMediaInfo&> SignalMediaMonitor;
+
+  bool SendIntraFrame();
+  bool RequestIntraFrame();
+
+  sigslot::signal3<VideoChannel*, uint32, VideoMediaChannel::Error>
+      SignalMediaError;
+
+ private:
+  // overrides from BaseChannel
+  virtual void ChangeState();
+  virtual const MediaContentDescription* GetFirstContent(
+      const SessionDescription* sdesc);
+  virtual bool SetLocalContent_w(const MediaContentDescription* content,
+                                 ContentAction action);
+  virtual bool SetRemoteContent_w(const MediaContentDescription* content,
+                                  ContentAction action);
+
+  void AddStream_w(uint32 ssrc, uint32 voice_ssrc);
+  void RemoveStream_w(uint32 ssrc);
+
+  void SendIntraFrame_w() {
+    media_channel()->SendIntraFrame();
+  }
+  void RequestIntraFrame_w() {
+    media_channel()->RequestIntraFrame();
+  }
+
+  struct RenderMessageData : public talk_base::MessageData {
+    RenderMessageData(uint32 s, VideoRenderer* r) : ssrc(s), renderer(r) {}
+    uint32 ssrc;
+    VideoRenderer* renderer;
+  };
+
+
+  void SetRenderer_w(uint32 ssrc, VideoRenderer* renderer);
+
+
+  virtual void OnMessage(talk_base::Message *pmsg);
+  virtual void OnConnectionMonitorUpdate(
+      SocketMonitor *monitor, const std::vector<ConnectionInfo> &infos);
+  virtual void OnMediaMonitorUpdate(
+      VideoMediaChannel *media_channel, const VideoMediaInfo& info);
+  void OnVideoChannelError(uint32 ssrc, VideoMediaChannel::Error error);
+
+  VoiceChannel *voice_channel_;
+  VideoRenderer *renderer_;
+  talk_base::scoped_ptr<VideoMediaMonitor> media_monitor_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_CHANNEL_H_
diff --git a/talk/session/phone/channelmanager.cc b/talk/session/phone/channelmanager.cc
new file mode 100644
index 0000000..d8c97cd
--- /dev/null
+++ b/talk/session/phone/channelmanager.cc
@@ -0,0 +1,769 @@
+/*
+ * 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 "talk/session/phone/channelmanager.h"
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <algorithm>
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/sigslotrepeater.h"
+#include "talk/base/stringencode.h"
+#include "talk/session/phone/mediaengine.h"
+#include "talk/session/phone/soundclip.h"
+
+namespace cricket {
+
+enum {
+  MSG_CREATEVOICECHANNEL = 1,
+  MSG_DESTROYVOICECHANNEL = 2,
+  MSG_SETAUDIOOPTIONS = 3,
+  MSG_SETOUTPUTVOLUME = 4,
+  MSG_SETLOCALMONITOR = 5,
+  MSG_SETVOICELOGGING = 6,
+  MSG_CREATEVIDEOCHANNEL = 11,
+  MSG_DESTROYVIDEOCHANNEL = 12,
+  MSG_SETVIDEOOPTIONS = 13,
+  MSG_SETLOCALRENDERER = 14,
+  MSG_SETDEFAULTVIDEOENCODERCONFIG = 15,
+  MSG_SETVIDEOLOGGING = 16,
+  MSG_CREATESOUNDCLIP = 17,
+  MSG_DESTROYSOUNDCLIP = 18,
+  MSG_CAMERASTARTED = 19,
+  MSG_SETVIDEOCAPTURE = 20,
+};
+
+struct CreationParams : public talk_base::MessageData {
+  CreationParams(BaseSession* session, const std::string& content_name,
+                 bool rtcp, VoiceChannel* voice_channel)
+      : session(session),
+        content_name(content_name),
+        rtcp(rtcp),
+        voice_channel(voice_channel),
+        video_channel(NULL) {}
+  BaseSession* session;
+  std::string content_name;
+  bool rtcp;
+  VoiceChannel* voice_channel;
+  VideoChannel* video_channel;
+};
+
+struct AudioOptions : public talk_base::MessageData {
+  AudioOptions(int o, const Device* in, const Device* out)
+      : options(o), in_device(in), out_device(out) {}
+  int options;
+  const Device* in_device;
+  const Device* out_device;
+  bool result;
+};
+
+struct VolumeLevel : public talk_base::MessageData {
+  explicit VolumeLevel(int l) : level(l), result(false) {}
+  int level;
+  bool result;
+};
+
+struct VideoOptions : public talk_base::MessageData {
+  explicit VideoOptions(const Device* d) : cam_device(d), result(false) {}
+  const Device* cam_device;
+  bool result;
+};
+
+struct DefaultVideoEncoderConfig : public talk_base::MessageData {
+  explicit DefaultVideoEncoderConfig(const VideoEncoderConfig& c)
+      : config(c), result(false) {}
+  VideoEncoderConfig config;
+  bool result;
+};
+
+struct LocalMonitor : public talk_base::MessageData {
+  explicit LocalMonitor(bool e) : enable(e), result(false) {}
+  bool enable;
+  bool result;
+};
+
+struct LocalRenderer : public talk_base::MessageData {
+  explicit LocalRenderer(VideoRenderer* r) : renderer(r), result(false) {}
+  VideoRenderer* renderer;
+  bool result;
+};
+
+struct LoggingOptions : public talk_base::MessageData {
+  explicit LoggingOptions(int lev, const char* f) : level(lev), filter(f) {}
+  int level;
+  std::string filter;
+};
+
+struct CaptureParams : public talk_base::MessageData {
+  explicit CaptureParams(bool c) : capture(c), result(CR_FAILURE) {}
+
+  bool capture;
+  CaptureResult result;
+};
+
+ChannelManager::ChannelManager(talk_base::Thread* worker_thread)
+    : media_engine_(MediaEngine::Create()),
+      device_manager_(new DeviceManager()),
+      initialized_(false),
+      main_thread_(talk_base::Thread::Current()),
+      worker_thread_(worker_thread),
+      audio_in_device_(DeviceManager::kDefaultDeviceName),
+      audio_out_device_(DeviceManager::kDefaultDeviceName),
+      audio_options_(MediaEngine::DEFAULT_AUDIO_OPTIONS),
+      local_renderer_(NULL),
+      capturing_(false),
+      monitoring_(false) {
+  Construct();
+}
+
+ChannelManager::ChannelManager(MediaEngine* me, DeviceManager* dm,
+                               talk_base::Thread* worker_thread)
+    : media_engine_(me),
+      device_manager_(dm),
+      initialized_(false),
+      main_thread_(talk_base::Thread::Current()),
+      worker_thread_(worker_thread),
+      audio_in_device_(DeviceManager::kDefaultDeviceName),
+      audio_out_device_(DeviceManager::kDefaultDeviceName),
+      audio_options_(MediaEngine::DEFAULT_AUDIO_OPTIONS),
+      local_renderer_(NULL),
+      capturing_(false),
+      monitoring_(false) {
+  Construct();
+}
+
+void ChannelManager::Construct() {
+  // Init the device manager immediately, and set up our default video device.
+  SignalDevicesChange.repeat(device_manager_->SignalDevicesChange);
+  device_manager_->Init();
+  // Set camera_device_ to the name of the default video capturer.
+  SetVideoOptions(DeviceManager::kDefaultDeviceName);
+
+  // Camera is started asynchronously, request callbacks when startup
+  // completes to be able to forward them to the rendering manager.
+  media_engine_->SignalVideoCaptureResult.connect(
+      this, &ChannelManager::OnVideoCaptureResult);
+}
+
+ChannelManager::~ChannelManager() {
+  if (initialized_)
+    Terminate();
+}
+
+int ChannelManager::GetCapabilities() {
+  return media_engine_->GetCapabilities() & device_manager_->GetCapabilities();
+}
+
+void ChannelManager::GetSupportedAudioCodecs(
+    std::vector<AudioCodec>* codecs) const {
+  codecs->clear();
+
+  for (std::vector<AudioCodec>::const_iterator it =
+           media_engine_->audio_codecs().begin();
+      it != media_engine_->audio_codecs().end(); ++it) {
+    codecs->push_back(*it);
+  }
+}
+
+void ChannelManager::GetSupportedVideoCodecs(
+    std::vector<VideoCodec>* codecs) const {
+  codecs->clear();
+
+  std::vector<VideoCodec>::const_iterator it;
+  for (it = media_engine_->video_codecs().begin();
+      it != media_engine_->video_codecs().end(); ++it) {
+    codecs->push_back(*it);
+  }
+}
+
+bool ChannelManager::Init() {
+  ASSERT(!initialized_);
+  if (initialized_) {
+    return false;
+  }
+
+  ASSERT(worker_thread_ != NULL);
+  if (worker_thread_ && worker_thread_->started()) {
+    if (media_engine_->Init()) {
+      initialized_ = true;
+
+      // Now that we're initialized, apply any stored preferences. A preferred
+      // device might have been unplugged. In this case, we fallback to the
+      // default device but keep the user preferences. The preferences are
+      // changed only when the Javascript FE changes them.
+      const std::string preferred_audio_in_device = audio_in_device_;
+      const std::string preferred_audio_out_device = audio_out_device_;
+      const std::string preferred_camera_device = camera_device_;
+      Device device;
+      if (!device_manager_->GetAudioInputDevice(audio_in_device_, &device)) {
+        LOG(LS_WARNING) << "The preferred microphone '" << audio_in_device_
+                        << "' is unavailable. Fall back to the default.";
+        audio_in_device_ = DeviceManager::kDefaultDeviceName;
+      }
+      if (!device_manager_->GetAudioOutputDevice(audio_out_device_, &device)) {
+        LOG(LS_WARNING) << "The preferred speaker '" << audio_out_device_
+                        << "' is unavailable. Fall back to the default.";
+        audio_out_device_ = DeviceManager::kDefaultDeviceName;
+      }
+      if (!device_manager_->GetVideoCaptureDevice(camera_device_, &device)) {
+        LOG(LS_WARNING) << "The preferred camera '" << camera_device_
+                        << "' is unavailable. Fall back to the default.";
+        camera_device_ = DeviceManager::kDefaultDeviceName;
+      }
+
+      if (!SetAudioOptions(audio_in_device_, audio_out_device_,
+                           audio_options_)) {
+        LOG(LS_WARNING) << "Failed to SetAudioOptions with"
+                        << " microphone: " << audio_in_device_
+                        << " speaker: " << audio_out_device_
+                        << " options: " << audio_options_;
+      }
+      if (!SetVideoOptions(camera_device_)) {
+        LOG(LS_WARNING) << "Failed to SetVideoOptions with camera: "
+                        << camera_device_;
+      }
+
+      // Restore the user preferences.
+      audio_in_device_ = preferred_audio_in_device;
+      audio_out_device_ = preferred_audio_out_device;
+      camera_device_ = preferred_camera_device;
+
+      // Now apply the default video codec that has been set earlier.
+      if (default_video_encoder_config_.max_codec.id != 0) {
+        SetDefaultVideoEncoderConfig(default_video_encoder_config_);
+      }
+      // And the local renderer.
+      if (local_renderer_) {
+        SetLocalRenderer(local_renderer_);
+      }
+    }
+  }
+  return initialized_;
+}
+
+void ChannelManager::Terminate() {
+  ASSERT(initialized_);
+  if (!initialized_) {
+    return;
+  }
+
+  // Need to destroy the voice/video channels
+  while (!video_channels_.empty()) {
+    DestroyVideoChannel_w(video_channels_.back());
+  }
+  while (!voice_channels_.empty()) {
+    DestroyVoiceChannel_w(voice_channels_.back());
+  }
+  while (!soundclips_.empty()) {
+    DestroySoundclip_w(soundclips_.back());
+  }
+
+  media_engine_->Terminate();
+  initialized_ = false;
+}
+
+VoiceChannel* ChannelManager::CreateVoiceChannel(
+    BaseSession* session, const std::string& content_name, bool rtcp) {
+  CreationParams params(session, content_name, rtcp, NULL);
+  return (Send(MSG_CREATEVOICECHANNEL, &params)) ? params.voice_channel : NULL;
+}
+
+VoiceChannel* ChannelManager::CreateVoiceChannel_w(
+    BaseSession* session, const std::string& content_name, bool rtcp) {
+  talk_base::CritScope cs(&crit_);
+
+  // This is ok to alloc from a thread other than the worker thread
+  ASSERT(initialized_);
+  VoiceMediaChannel* media_channel = media_engine_->CreateChannel();
+  if (media_channel == NULL)
+    return NULL;
+
+  VoiceChannel* voice_channel = new VoiceChannel(
+      worker_thread_, media_engine_.get(), media_channel,
+      session, content_name, rtcp);
+  voice_channels_.push_back(voice_channel);
+  return voice_channel;
+}
+
+void ChannelManager::DestroyVoiceChannel(VoiceChannel* voice_channel) {
+  if (voice_channel) {
+    talk_base::TypedMessageData<VoiceChannel *> data(voice_channel);
+    Send(MSG_DESTROYVOICECHANNEL, &data);
+  }
+}
+
+void ChannelManager::DestroyVoiceChannel_w(VoiceChannel* voice_channel) {
+  talk_base::CritScope cs(&crit_);
+  // Destroy voice channel.
+  ASSERT(initialized_);
+  VoiceChannels::iterator it = std::find(voice_channels_.begin(),
+      voice_channels_.end(), voice_channel);
+  ASSERT(it != voice_channels_.end());
+  if (it == voice_channels_.end())
+    return;
+
+  voice_channels_.erase(it);
+  delete voice_channel;
+}
+
+VideoChannel* ChannelManager::CreateVideoChannel(
+    BaseSession* session, const std::string& content_name, bool rtcp,
+    VoiceChannel* voice_channel) {
+  CreationParams params(session, content_name, rtcp, voice_channel);
+  return (Send(MSG_CREATEVIDEOCHANNEL, &params)) ? params.video_channel : NULL;
+}
+
+VideoChannel* ChannelManager::CreateVideoChannel_w(
+    BaseSession* session, const std::string& content_name, bool rtcp,
+    VoiceChannel* voice_channel) {
+  talk_base::CritScope cs(&crit_);
+
+  // This is ok to alloc from a thread other than the worker thread
+  ASSERT(initialized_);
+  VideoMediaChannel* media_channel =
+      // voice_channel can be NULL in case of NullVoiceEngine.
+      media_engine_->CreateVideoChannel(voice_channel ?
+          voice_channel->media_channel() : NULL);
+  if (media_channel == NULL)
+    return NULL;
+
+  VideoChannel* video_channel = new VideoChannel(
+      worker_thread_, media_engine_.get(), media_channel,
+      session, content_name, rtcp, voice_channel);
+  video_channels_.push_back(video_channel);
+  return video_channel;
+}
+
+void ChannelManager::DestroyVideoChannel(VideoChannel* video_channel) {
+  if (video_channel) {
+    talk_base::TypedMessageData<VideoChannel *> data(video_channel);
+    Send(MSG_DESTROYVIDEOCHANNEL, &data);
+  }
+}
+
+void ChannelManager::DestroyVideoChannel_w(VideoChannel *video_channel) {
+  talk_base::CritScope cs(&crit_);
+  // Destroy voice channel.
+  ASSERT(initialized_);
+  VideoChannels::iterator it = std::find(video_channels_.begin(),
+      video_channels_.end(), video_channel);
+  ASSERT(it != video_channels_.end());
+  if (it == video_channels_.end())
+    return;
+
+  video_channels_.erase(it);
+  delete video_channel;
+}
+
+Soundclip* ChannelManager::CreateSoundclip() {
+  talk_base::TypedMessageData<Soundclip*> data(NULL);
+  Send(MSG_CREATESOUNDCLIP, &data);
+  return data.data();
+}
+
+Soundclip* ChannelManager::CreateSoundclip_w() {
+  talk_base::CritScope cs(&crit_);
+
+  ASSERT(initialized_);
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+  SoundclipMedia* soundclip_media = media_engine_->CreateSoundclip();
+  if (!soundclip_media) {
+    return NULL;
+  }
+
+  Soundclip* soundclip = new Soundclip(worker_thread_, soundclip_media);
+  soundclips_.push_back(soundclip);
+  return soundclip;
+}
+
+void ChannelManager::DestroySoundclip(Soundclip* soundclip) {
+  if (soundclip) {
+    talk_base::TypedMessageData<Soundclip*> data(soundclip);
+    Send(MSG_DESTROYSOUNDCLIP, &data);
+  }
+}
+
+void ChannelManager::DestroySoundclip_w(Soundclip* soundclip) {
+  talk_base::CritScope cs(&crit_);
+  // Destroy soundclip.
+  ASSERT(initialized_);
+  Soundclips::iterator it = std::find(soundclips_.begin(),
+      soundclips_.end(), soundclip);
+  ASSERT(it != soundclips_.end());
+  if (it == soundclips_.end())
+    return;
+
+  soundclips_.erase(it);
+  delete soundclip;
+}
+
+bool ChannelManager::GetAudioOptions(std::string* in_name,
+                                     std::string* out_name, int* opts) {
+  *in_name = audio_in_device_;
+  *out_name = audio_out_device_;
+  *opts = audio_options_;
+  return true;
+}
+
+bool ChannelManager::SetAudioOptions(const std::string& in_name,
+                                     const std::string& out_name, int opts) {
+  // Get device ids from DeviceManager.
+  Device in_dev, out_dev;
+  if (!device_manager_->GetAudioInputDevice(in_name, &in_dev) ||
+      !device_manager_->GetAudioOutputDevice(out_name, &out_dev)) {
+    LOG(LS_WARNING) << "Device manager can't find selected device";
+    return false;
+  }
+
+  // If we're initialized, pass the settings to the media engine.
+  bool ret = true;
+  if (initialized_) {
+    AudioOptions options(opts, &in_dev, &out_dev);
+    ret = (Send(MSG_SETAUDIOOPTIONS, &options) && options.result);
+  }
+
+  // If all worked well, save the values for use in GetAudioOptions.
+  if (ret) {
+    audio_options_ = opts;
+    audio_in_device_ = in_name;
+    audio_out_device_ = out_name;
+  }
+  return ret;
+}
+
+bool ChannelManager::SetAudioOptions_w(int opts, const Device* in_dev,
+    const Device* out_dev) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  ASSERT(initialized_);
+
+  // Set audio options
+  bool ret = media_engine_->SetAudioOptions(opts);
+
+  // Set the audio devices
+  if (ret) {
+    talk_base::CritScope cs(&crit_);
+    ret = media_engine_->SetSoundDevices(in_dev, out_dev);
+  }
+
+  return ret;
+}
+
+bool ChannelManager::SetOutputVolume(int level) {
+  VolumeLevel volume(level);
+  return (Send(MSG_SETOUTPUTVOLUME, &volume) && volume.result);
+}
+
+bool ChannelManager::SetOutputVolume_w(int level) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  ASSERT(initialized_);
+  return media_engine_->SetOutputVolume(level);
+}
+
+bool ChannelManager::GetVideoOptions(std::string* cam_name) {
+  *cam_name = camera_device_;
+  return true;
+}
+
+bool ChannelManager::SetVideoOptions(const std::string& cam_name) {
+  Device device;
+  if (!device_manager_->GetVideoCaptureDevice(cam_name, &device)) {
+    LOG(LS_WARNING) << "Device manager can't find camera: " << cam_name;
+    return false;
+  }
+
+  // If we're running, tell the media engine about it.
+  bool ret = true;
+  if (initialized_) {
+    VideoOptions options(&device);
+    ret = (Send(MSG_SETVIDEOOPTIONS, &options) && options.result);
+  }
+
+  // If everything worked, retain the name of the selected camera.
+  if (ret) {
+    camera_device_ = device.name;
+  }
+  return ret;
+}
+
+bool ChannelManager::SetVideoOptions_w(const Device* cam_device) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  ASSERT(initialized_);
+
+  // Set the video input device
+  return media_engine_->SetVideoCaptureDevice(cam_device);
+}
+
+bool ChannelManager::SetDefaultVideoEncoderConfig(const VideoEncoderConfig& c) {
+  bool ret = true;
+  if (initialized_) {
+    DefaultVideoEncoderConfig config(c);
+    ret = Send(MSG_SETDEFAULTVIDEOENCODERCONFIG, &config) && config.result;
+  }
+  if (ret) {
+    default_video_encoder_config_ = c;
+  }
+  return ret;
+}
+
+bool ChannelManager::SetDefaultVideoEncoderConfig_w(
+    const VideoEncoderConfig& c) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  ASSERT(initialized_);
+  return media_engine_->SetDefaultVideoEncoderConfig(c);
+}
+
+bool ChannelManager::SetLocalMonitor(bool enable) {
+  LocalMonitor monitor(enable);
+  bool ret = Send(MSG_SETLOCALMONITOR, &monitor) && monitor.result;
+  if (ret) {
+    monitoring_ = enable;
+  }
+  return ret;
+}
+
+bool ChannelManager::SetLocalMonitor_w(bool enable) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  ASSERT(initialized_);
+  return media_engine_->SetLocalMonitor(enable);
+}
+
+bool ChannelManager::SetLocalRenderer(VideoRenderer* renderer) {
+  bool ret = true;
+  if (initialized_) {
+    LocalRenderer local(renderer);
+    ret = (Send(MSG_SETLOCALRENDERER, &local) && local.result);
+  }
+  if (ret) {
+    local_renderer_ = renderer;
+  }
+  return ret;
+}
+
+bool ChannelManager::SetLocalRenderer_w(VideoRenderer* renderer) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  ASSERT(initialized_);
+  return media_engine_->SetLocalRenderer(renderer);
+}
+
+CaptureResult ChannelManager::SetVideoCapture(bool capture) {
+  bool ret;
+  CaptureParams capture_params(capture);
+  ret = (Send(MSG_SETVIDEOCAPTURE, &capture_params) &&
+         (capture_params.result != CR_FAILURE));
+  if (ret) {
+    capturing_ = capture;
+  }
+  return capture_params.result;
+}
+
+CaptureResult ChannelManager::SetVideoCapture_w(bool capture) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  ASSERT(initialized_);
+  return media_engine_->SetVideoCapture(capture);
+}
+
+void ChannelManager::SetVoiceLogging(int level, const char* filter) {
+  SetMediaLogging(false, level, filter);
+}
+
+void ChannelManager::SetVideoLogging(int level, const char* filter) {
+  SetMediaLogging(true, level, filter);
+}
+
+void ChannelManager::SetMediaLogging(bool video, int level,
+                                     const char* filter) {
+  // Can be called before initialization; in this case, the worker function
+  // is simply called on the main thread.
+  if (initialized_) {
+    LoggingOptions options(level, filter);
+    Send((video) ? MSG_SETVIDEOLOGGING : MSG_SETVOICELOGGING, &options);
+  } else {
+    SetMediaLogging_w(video, level, filter);
+  }
+}
+
+void ChannelManager::SetMediaLogging_w(bool video, int level,
+                                       const char* filter) {
+  // Can be called before initialization
+  ASSERT(worker_thread_ == talk_base::Thread::Current() || !initialized_);
+  if (video) {
+    media_engine_->SetVideoLogging(level, filter);
+  } else {
+    media_engine_->SetVoiceLogging(level, filter);
+  }
+}
+
+bool ChannelManager::Send(uint32 id, talk_base::MessageData* data) {
+  if (!worker_thread_ || !initialized_) return false;
+  worker_thread_->Send(this, id, data);
+  return true;
+}
+
+void ChannelManager::OnVideoCaptureResult(CaptureResult result) {
+  capturing_ = result == CR_SUCCESS;
+  main_thread_->Post(this, MSG_CAMERASTARTED,
+                     new talk_base::TypedMessageData<CaptureResult>(result));
+}
+
+void ChannelManager::OnMessage(talk_base::Message* message) {
+  talk_base::MessageData* data = message->pdata;
+  switch (message->message_id) {
+    case MSG_CREATEVOICECHANNEL: {
+      CreationParams* p = static_cast<CreationParams*>(data);
+      p->voice_channel =
+          CreateVoiceChannel_w(p->session, p->content_name, p->rtcp);
+      break;
+    }
+    case MSG_DESTROYVOICECHANNEL: {
+      VoiceChannel* p = static_cast<talk_base::TypedMessageData<VoiceChannel*>*>
+          (data)->data();
+      DestroyVoiceChannel_w(p);
+      break;
+    }
+    case MSG_CREATEVIDEOCHANNEL: {
+      CreationParams* p = static_cast<CreationParams*>(data);
+      p->video_channel = CreateVideoChannel_w(p->session, p->content_name,
+                                              p->rtcp, p->voice_channel);
+      break;
+    }
+    case MSG_DESTROYVIDEOCHANNEL: {
+      VideoChannel* p = static_cast<talk_base::TypedMessageData<VideoChannel*>*>
+          (data)->data();
+      DestroyVideoChannel_w(p);
+      break;
+    }
+    case MSG_CREATESOUNDCLIP: {
+      talk_base::TypedMessageData<Soundclip*> *p =
+          static_cast<talk_base::TypedMessageData<Soundclip*>*>(data);
+      p->data() = CreateSoundclip_w();
+      break;
+    }
+    case MSG_DESTROYSOUNDCLIP: {
+      talk_base::TypedMessageData<Soundclip*> *p =
+          static_cast<talk_base::TypedMessageData<Soundclip*>*>(data);
+      DestroySoundclip_w(p->data());
+      break;
+    }
+    case MSG_SETAUDIOOPTIONS: {
+      AudioOptions* p = static_cast<AudioOptions*>(data);
+      p->result = SetAudioOptions_w(p->options,
+                                    p->in_device, p->out_device);
+      break;
+    }
+    case MSG_SETOUTPUTVOLUME: {
+      VolumeLevel* p = static_cast<VolumeLevel*>(data);
+      p->result = SetOutputVolume_w(p->level);
+      break;
+    }
+    case MSG_SETLOCALMONITOR: {
+      LocalMonitor* p = static_cast<LocalMonitor*>(data);
+      p->result = SetLocalMonitor_w(p->enable);
+      break;
+    }
+    case MSG_SETVIDEOOPTIONS: {
+      VideoOptions* p = static_cast<VideoOptions*>(data);
+      p->result = SetVideoOptions_w(p->cam_device);
+      break;
+    }
+    case MSG_SETDEFAULTVIDEOENCODERCONFIG: {
+      DefaultVideoEncoderConfig* p =
+          static_cast<DefaultVideoEncoderConfig*>(data);
+      p->result = SetDefaultVideoEncoderConfig_w(p->config);
+      break;
+    }
+    case MSG_SETLOCALRENDERER: {
+      LocalRenderer* p = static_cast<LocalRenderer*>(data);
+      p->result = SetLocalRenderer_w(p->renderer);
+      break;
+    }
+    case MSG_SETVIDEOCAPTURE: {
+      CaptureParams* p = static_cast<CaptureParams*>(data);
+      p->result = SetVideoCapture_w(p->capture);
+      break;
+    }
+    case MSG_SETVOICELOGGING:
+    case MSG_SETVIDEOLOGGING: {
+      LoggingOptions* p = static_cast<LoggingOptions*>(data);
+      bool video = (message->message_id == MSG_SETVIDEOLOGGING);
+      SetMediaLogging_w(video, p->level, p->filter.c_str());
+      break;
+    }
+    case MSG_CAMERASTARTED: {
+      talk_base::TypedMessageData<CaptureResult>* data =
+          static_cast<talk_base::TypedMessageData<CaptureResult>*>(
+              message->pdata);
+      SignalVideoCaptureResult(data->data());
+      delete data;
+      break;
+    }
+  }
+}
+
+static void GetDeviceNames(const std::vector<Device>& devs,
+                           std::vector<std::string>* names) {
+  names->clear();
+  for (size_t i = 0; i < devs.size(); ++i) {
+    names->push_back(devs[i].name);
+  }
+}
+
+bool ChannelManager::GetAudioInputDevices(std::vector<std::string>* names) {
+  names->clear();
+  std::vector<Device> devs;
+  bool ret = device_manager_->GetAudioInputDevices(&devs);
+  if (ret)
+    GetDeviceNames(devs, names);
+
+  return ret;
+}
+
+bool ChannelManager::GetAudioOutputDevices(std::vector<std::string>* names) {
+  names->clear();
+  std::vector<Device> devs;
+  bool ret = device_manager_->GetAudioOutputDevices(&devs);
+  if (ret)
+    GetDeviceNames(devs, names);
+
+  return ret;
+}
+
+bool ChannelManager::GetVideoCaptureDevices(std::vector<std::string>* names) {
+  names->clear();
+  std::vector<Device> devs;
+  bool ret = device_manager_->GetVideoCaptureDevices(&devs);
+  if (ret)
+    GetDeviceNames(devs, names);
+
+  return ret;
+}
+
+}  // namespace cricket
diff --git a/talk/session/phone/channelmanager.h b/talk/session/phone/channelmanager.h
new file mode 100644
index 0000000..dfcfffe
--- /dev/null
+++ b/talk/session/phone/channelmanager.h
@@ -0,0 +1,212 @@
+/*
+ * 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 TALK_SESSION_PHONE_CHANNELMANAGER_H_
+#define TALK_SESSION_PHONE_CHANNELMANAGER_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/criticalsection.h"
+#include "talk/base/sigslotrepeater.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/session.h"
+#include "talk/session/phone/voicechannel.h"
+#include "talk/session/phone/mediaengine.h"
+#include "talk/session/phone/devicemanager.h"
+
+namespace cricket {
+
+class Soundclip;
+class VoiceChannel;
+
+// ChannelManager allows the MediaEngine to run on a separate thread, and takes
+// care of marshalling calls between threads. It also creates and keeps track of
+// voice and video channels; by doing so, it can temporarily pause all the
+// channels when a new audio or video device is chosen. The voice and video
+// channels are stored in separate vectors, to easily allow operations on just
+// voice or just video channels.
+// ChannelManager also allows the application to discover what devices it has
+// using device manager.
+class ChannelManager : public talk_base::MessageHandler,
+                       public sigslot::has_slots<> {
+ public:
+  // Creates the channel manager, and specifies the worker thread to use.
+  explicit ChannelManager(talk_base::Thread* worker);
+  // For testing purposes. Allows the media engine and dev manager to be mocks.
+  // The ChannelManager takes ownership of these objects.
+  ChannelManager(MediaEngine* me, DeviceManager* dm, talk_base::Thread* worker);
+  ~ChannelManager();
+
+  // Accessors for the worker thread, allowing it to be set after construction,
+  // but before Init. set_worker_thread will return false if called after Init.
+  talk_base::Thread* worker_thread() const { return worker_thread_; }
+  bool set_worker_thread(talk_base::Thread* thread) {
+    if (initialized_) return false;
+    worker_thread_ = thread;
+    return true;
+  }
+
+  // Gets capabilities. Can be called prior to starting the media engine.
+  int GetCapabilities();
+
+  // Retrieves the list of supported audio & video codec types.
+  // Can be called before starting the media engine.
+  void GetSupportedAudioCodecs(std::vector<AudioCodec>* codecs) const;
+  void GetSupportedVideoCodecs(std::vector<VideoCodec>* codecs) const;
+
+  // Determines if a specific audio or video codec is supported.
+  // Can be called before starting the media engine.
+  bool FindAudioCodec(const AudioCodec& codec) const {
+    return media_engine_->FindAudioCodec(codec);
+  }
+  bool FindVideoCodec(const VideoCodec& video_codec) const {
+    return media_engine_->FindVideoCodec(video_codec);
+  }
+
+  // Indicates whether the media engine is started.
+  bool initialized() const { return initialized_; }
+  // Starts up the media engine.
+  bool Init();
+  // TODO: Remove this temporary API once Flute is updated.
+  bool Init(talk_base::Thread* thread) {
+    return set_worker_thread(thread) && Init();
+  }
+  // Shuts down the media engine.
+  void Terminate();
+
+  // The operations below all occur on the worker thread.
+
+  // Creates a voice channel, to be associated with the specified session.
+  VoiceChannel* CreateVoiceChannel(
+      BaseSession* session, const std::string& content_name, bool rtcp);
+  // Destroys a voice channel created with the Create API.
+  void DestroyVoiceChannel(VoiceChannel* voice_channel);
+  // Creates a video channel, synced with the specified voice channel, and
+  // associated with the specified session.
+  VideoChannel* CreateVideoChannel(
+      BaseSession* session, const std::string& content_name, bool rtcp,
+      VoiceChannel* voice_channel);
+  // Destroys a video channel created with the Create API.
+  void DestroyVideoChannel(VideoChannel* video_channel);
+
+  // Creates a soundclip.
+  Soundclip* CreateSoundclip();
+  // Destroys a soundclip created with the Create API.
+  void DestroySoundclip(Soundclip* soundclip);
+
+  // Indicates whether any channels exist.
+  bool has_channels() const {
+    return (!voice_channels_.empty() || !video_channels_.empty() ||
+            !soundclips_.empty());
+  }
+
+  // Configures the audio and video devices.
+  bool GetAudioOptions(std::string* wave_in_device,
+                       std::string* wave_out_device, int* opts);
+  bool SetAudioOptions(const std::string& wave_in_device,
+                       const std::string& wave_out_device, int opts);
+  bool SetOutputVolume(int level);
+  bool GetVideoOptions(std::string* cam_device);
+  bool SetVideoOptions(const std::string& cam_device);
+  bool SetDefaultVideoEncoderConfig(const VideoEncoderConfig& config);
+
+  // Starts/stops the local microphone and enables polling of the input level.
+  bool SetLocalMonitor(bool enable);
+  bool monitoring() const { return monitoring_; }
+  // Sets the local renderer where to renderer the local camera.
+  bool SetLocalRenderer(VideoRenderer* renderer);
+  // Starts and stops the local camera and renders it to the local renderer.
+  CaptureResult SetVideoCapture(bool capture);
+  bool capturing() const { return capturing_; }
+
+  // Configures the logging output of the mediaengine(s).
+  void SetVoiceLogging(int level, const char* filter);
+  void SetVideoLogging(int level, const char* filter);
+
+  // The operations below occur on the main thread.
+
+  bool GetAudioInputDevices(std::vector<std::string>* names);
+  bool GetAudioOutputDevices(std::vector<std::string>* names);
+  bool GetVideoCaptureDevices(std::vector<std::string>* names);
+  sigslot::repeater0<> SignalDevicesChange;
+  sigslot::signal1<CaptureResult> SignalVideoCaptureResult;
+
+ private:
+  typedef std::vector<VoiceChannel*> VoiceChannels;
+  typedef std::vector<VideoChannel*> VideoChannels;
+  typedef std::vector<Soundclip*> Soundclips;
+
+  void Construct();
+  bool Send(uint32 id, talk_base::MessageData* pdata);
+  VoiceChannel* CreateVoiceChannel_w(
+      BaseSession* session, const std::string& content_name, bool rtcp);
+  void DestroyVoiceChannel_w(VoiceChannel* voice_channel);
+  VideoChannel* CreateVideoChannel_w(
+      BaseSession* session, const std::string& content_name, bool rtcp,
+      VoiceChannel* voice_channel);
+  void DestroyVideoChannel_w(VideoChannel* video_channel);
+  Soundclip* CreateSoundclip_w();
+  void DestroySoundclip_w(Soundclip* soundclip);
+  bool SetAudioOptions_w(int opts, const Device* in_dev,
+                         const Device* out_dev);
+  bool SetOutputVolume_w(int level);
+  bool SetLocalMonitor_w(bool enable);
+  bool SetVideoOptions_w(const Device* cam_device);
+  bool SetDefaultVideoEncoderConfig_w(const VideoEncoderConfig& config);
+  bool SetLocalRenderer_w(VideoRenderer* renderer);
+  CaptureResult SetVideoCapture_w(bool capture);
+  void SetMediaLogging(bool video, int level, const char* filter);
+  void SetMediaLogging_w(bool video, int level, const char* filter);
+  void OnVideoCaptureResult(CaptureResult result);
+  void OnMessage(talk_base::Message *message);
+
+  talk_base::CriticalSection crit_;
+  talk_base::scoped_ptr<MediaEngine> media_engine_;
+  talk_base::scoped_ptr<DeviceManager> device_manager_;
+  bool initialized_;
+  talk_base::Thread* main_thread_;
+  talk_base::Thread* worker_thread_;
+
+  VoiceChannels voice_channels_;
+  VideoChannels video_channels_;
+  Soundclips soundclips_;
+
+  std::string audio_in_device_;
+  std::string audio_out_device_;
+  int audio_options_;
+  std::string camera_device_;
+  VideoEncoderConfig default_video_encoder_config_;
+  VideoRenderer* local_renderer_;
+
+  bool capturing_;
+  bool monitoring_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_CHANNELMANAGER_H_
diff --git a/talk/session/phone/codec.cc b/talk/session/phone/codec.cc
new file mode 100644
index 0000000..01d1a7b
--- /dev/null
+++ b/talk/session/phone/codec.cc
@@ -0,0 +1,78 @@
+/*
+ * 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/session/phone/codec.h"
+#include <sstream>
+
+namespace cricket {
+
+static const int kMaxStaticPayloadId = 95;
+
+bool AudioCodec::Matches(int payload, const std::string& nm) const {
+  // Match the codec id/name based on the typical static/dynamic name rules.
+  return (payload <= kMaxStaticPayloadId) ? (id == payload) : (name == nm);
+}
+
+bool AudioCodec::Matches(const AudioCodec& codec) const {
+  // If a nonzero clockrate is specified, it must match the actual clockrate.
+  // If a nonzero bitrate is specified, it must match the actual bitrate,
+  // unless the codec is VBR (-1), where we just force the supplied value.
+  // The number of channels must match exactly.
+  // Preference is ignored.
+  // TODO: Treat a zero clockrate as 8000Hz, the RTP default clockrate.
+  return Matches(codec.id, codec.name) &&
+      ((codec.clockrate == 0 /*&& clockrate == 8000*/) ||
+          clockrate == codec.clockrate) &&
+      (codec.bitrate == 0 || bitrate == -1 || bitrate == codec.bitrate) &&
+      (codec.channels == 0 || channels == codec.channels);
+}
+
+std::string AudioCodec::ToString() const {
+  std::ostringstream os;
+  os << "AudioCodec[" << id << ":" << name << ":" << clockrate << ":" << bitrate
+     << ":" << channels << ":" << preference << "]";
+  return os.str();
+}
+
+bool VideoCodec::Matches(int payload, const std::string& nm) const {
+  // Match the codec id/name based on the typical static/dynamic name rules.
+  return (payload <= kMaxStaticPayloadId) ? (id == payload) : (name == nm);
+}
+
+bool VideoCodec::Matches(const VideoCodec& codec) const {
+  // Only the id and name are matched.
+  return Matches(codec.id, codec.name);
+}
+
+std::string VideoCodec::ToString() const {
+  std::ostringstream os;
+  os << "VideoCodec[" << id << ":" << name << ":" << width << ":" << height
+     << ":" << framerate << ":" << preference << "]";
+  return os.str();
+}
+
+}  // namespace cricket
diff --git a/talk/session/phone/codec.h b/talk/session/phone/codec.h
new file mode 100644
index 0000000..8536dbb
--- /dev/null
+++ b/talk/session/phone/codec.h
@@ -0,0 +1,182 @@
+/*
+ * 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.
+ */
+
+#ifndef TALK_SESSION_PHONE_CODEC_H_
+#define TALK_SESSION_PHONE_CODEC_H_
+
+#include <string>
+
+namespace cricket {
+
+struct AudioCodec {
+  int id;
+  std::string name;
+  int clockrate;
+  int bitrate;
+  int channels;
+
+  int preference;
+
+  // Creates a codec with the given parameters.
+  AudioCodec(int pt, const std::string& nm, int cr, int br, int cs, int pr)
+      : id(pt), name(nm), clockrate(cr), bitrate(br),
+        channels(cs), preference(pr) {}
+
+  // Creates an empty codec.
+  AudioCodec() : id(0), clockrate(0), bitrate(0), channels(0), preference(0) {}
+
+  // Indicates if this codec is compatible with the specified codec.
+  bool Matches(int payload, const std::string& nm) const;
+  bool Matches(const AudioCodec& codec) const;
+
+  static bool Preferable(const AudioCodec& first, const AudioCodec& other) {
+    return first.preference > other.preference;
+  }
+
+  std::string ToString() const;
+
+  AudioCodec& operator=(const AudioCodec& c) {
+    this->id = c.id;  // id is reserved in objective-c
+    name = c.name;
+    clockrate = c.clockrate;
+    bitrate = c.bitrate;
+    channels = c.channels;
+    preference =  c.preference;
+    return *this;
+  }
+
+  bool operator==(const AudioCodec& c) const {
+    return this->id == c.id &&  // id is reserved in objective-c
+           name == c.name &&
+           clockrate == c.clockrate &&
+           bitrate == c.bitrate &&
+           channels == c.channels &&
+           preference == c.preference;
+  }
+
+  bool operator!=(const AudioCodec& c) const {
+    return !(*this == c);
+  }
+};
+
+struct VideoCodec {
+  int id;
+  std::string name;
+  int width;
+  int height;
+  int framerate;
+
+  int preference;
+
+  // Creates a codec with the given parameters.
+  VideoCodec(int pt, const std::string& nm, int w, int h, int fr, int pr)
+      : id(pt), name(nm), width(w), height(h), framerate(fr), preference(pr) {}
+
+  // Creates an empty codec.
+  VideoCodec()
+      : id(0), width(0), height(0), framerate(0), preference(0) {}
+
+  bool Matches(int payload, const std::string& nm) const;
+  bool Matches(const VideoCodec& codec) const;
+
+  static bool Preferable(const VideoCodec& first, const VideoCodec& other) {
+    return first.preference > other.preference;
+  }
+
+  std::string ToString() const;
+
+  VideoCodec& operator=(const VideoCodec& c) {
+    this->id = c.id;  // id is reserved in objective-c
+    name = c.name;
+    width = c.width;
+    height = c.height;
+    framerate = c.framerate;
+    preference =  c.preference;
+    return *this;
+  }
+
+  bool operator==(const VideoCodec& c) const {
+    return this->id == c.id &&  // id is reserved in objective-c
+           name == c.name &&
+           width == c.width &&
+           height == c.height &&
+           framerate == c.framerate &&
+           preference == c.preference;
+  }
+
+  bool operator!=(const VideoCodec& c) const {
+    return !(*this == c);
+  }
+};
+
+struct VideoEncoderConfig {
+  static const int kDefaultMaxThreads = -1;
+  static const int kDefaultCpuProfile = -1;
+
+  VideoEncoderConfig()
+      : max_codec(),
+        num_threads(kDefaultMaxThreads),
+        cpu_profile(kDefaultCpuProfile) {
+  }
+
+  VideoEncoderConfig(const VideoCodec& c)
+      : max_codec(c),
+        num_threads(kDefaultMaxThreads),
+        cpu_profile(kDefaultCpuProfile) {
+  }
+
+  VideoEncoderConfig(const VideoCodec& c, int t, int p)
+      : max_codec(c),
+        num_threads(t),
+        cpu_profile(p) {
+  }
+
+  VideoEncoderConfig& operator=(const VideoEncoderConfig& config) {
+    max_codec = config.max_codec;
+    num_threads = config.num_threads;
+    cpu_profile = config.cpu_profile;
+    return *this;
+  }
+
+  bool operator==(const VideoEncoderConfig& config) const {
+    return max_codec == config.max_codec &&
+           num_threads == config.num_threads &&
+           cpu_profile == config.cpu_profile;
+  }
+
+  bool operator!=(const VideoEncoderConfig& config) const {
+    return !(*this == config);
+  }
+
+  VideoCodec max_codec;
+  int num_threads;
+  int cpu_profile;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_CODEC_H_
diff --git a/talk/session/phone/cryptoparams.h b/talk/session/phone/cryptoparams.h
new file mode 100644
index 0000000..15e94a0
--- /dev/null
+++ b/talk/session/phone/cryptoparams.h
@@ -0,0 +1,54 @@
+/*
+ * 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_SESSION_PHONE_CRYPTOPARAMS_H_
+#define TALK_SESSION_PHONE_CRYPTOPARAMS_H_
+
+#include <string>
+
+namespace cricket {
+
+// Parameters for SRTP negotiation, as described in RFC 4568.
+struct CryptoParams {
+  CryptoParams() : tag(0) {}
+  CryptoParams(int t, const std::string& cs,
+               const std::string& kp, const std::string& sp)
+      : tag(t), cipher_suite(cs), key_params(kp), session_params(sp) {}
+
+  bool Matches(const CryptoParams& params) const {
+    return (tag == params.tag && cipher_suite == params.cipher_suite);
+  }
+
+  int tag;
+  std::string cipher_suite;
+  std::string key_params;
+  std::string session_params;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_CRYPTOPARAMS_H_
diff --git a/talk/session/phone/devicemanager.cc b/talk/session/phone/devicemanager.cc
new file mode 100644
index 0000000..cb97301
--- /dev/null
+++ b/talk/session/phone/devicemanager.cc
@@ -0,0 +1,989 @@
+/*
+ * 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 "talk/session/phone/devicemanager.h"
+
+#if WIN32
+#include <atlbase.h>
+#include <dbt.h>
+#include <strmif.h>  // must come before ks.h
+#include <ks.h>
+#include <ksmedia.h>
+#define INITGUID  // For PKEY_AudioEndpoint_GUID
+#include <mmdeviceapi.h>
+#include <functiondiscoverykeys_devpkey.h>
+#include <uuids.h>
+#include "talk/base/win32.h"  // ToUtf8
+#include "talk/base/win32window.h"
+#elif OSX
+#include <CoreAudio/CoreAudio.h>
+#include <QuickTime/QuickTime.h>
+#elif LINUX
+#include <libudev.h>
+#include <unistd.h>
+#include "talk/base/linux.h"
+#include "talk/base/fileutils.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/stream.h"
+#include "talk/session/phone/libudevsymboltable.h"
+#include "talk/session/phone/v4llookup.h"
+#include "talk/sound/platformsoundsystem.h"
+#include "talk/sound/platformsoundsystemfactory.h"
+#include "talk/sound/sounddevicelocator.h"
+#include "talk/sound/soundsysteminterface.h"
+#endif
+
+#include "talk/base/logging.h"
+#include "talk/base/stringutils.h"
+#include "talk/session/phone/mediaengine.h"
+
+namespace cricket {
+// Initialize to empty string.
+const std::string DeviceManager::kDefaultDeviceName;
+
+#ifdef WIN32
+class DeviceWatcher : public talk_base::Win32Window {
+ public:
+  explicit DeviceWatcher(DeviceManager* dm);
+  bool Start();
+  void Stop();
+
+ private:
+  HDEVNOTIFY Register(REFGUID guid);
+  void Unregister(HDEVNOTIFY notify);
+  virtual bool OnMessage(UINT msg, WPARAM wp, LPARAM lp, LRESULT& result);
+
+  DeviceManager* manager_;
+  HDEVNOTIFY audio_notify_;
+  HDEVNOTIFY video_notify_;
+};
+#elif defined(LINUX)
+class DeviceWatcher : private talk_base::Dispatcher {
+ public:
+  explicit DeviceWatcher(DeviceManager* dm);
+  bool Start();
+  void Stop();
+
+ private:
+  virtual uint32 GetRequestedEvents();
+  virtual void OnPreEvent(uint32 ff);
+  virtual void OnEvent(uint32 ff, int err);
+  virtual int GetDescriptor();
+  virtual bool IsDescriptorClosed();
+
+  DeviceManager* manager_;
+  LibUDevSymbolTable libudev_;
+  struct udev* udev_;
+  struct udev_monitor* udev_monitor_;
+  bool registered_;
+};
+#define LATE(sym) LATESYM_GET(LibUDevSymbolTable, &libudev_, sym)
+#elif defined(OSX)
+class DeviceWatcher {
+ public:
+  explicit DeviceWatcher(DeviceManager* dm);
+  bool Start();
+  void Stop();
+ private:
+  DeviceManager* manager_;
+  void* impl_;
+};
+#elif defined(IOS) || defined(ANDROID)
+// We don't use DeviceWatcher on iOS or Android, so just stub out a noop class.
+class DeviceWatcher {
+ public:
+  explicit DeviceWatcher(DeviceManager* dm) {}
+  bool Start() { return true; }
+  void Stop() {}
+};
+#endif
+
+#if !defined(LINUX) && !defined(IOS)
+static bool ShouldDeviceBeIgnored(const std::string& device_name);
+#endif
+#ifndef OSX
+static bool GetVideoDevices(std::vector<Device>* out);
+#endif
+#if WIN32
+static const wchar_t kFriendlyName[] = L"FriendlyName";
+static const wchar_t kDevicePath[] = L"DevicePath";
+static const char kUsbDevicePathPrefix[] = "\\\\?\\usb";
+static bool GetDevices(const CLSID& catid, std::vector<Device>* out);
+static bool GetCoreAudioDevices(bool input, std::vector<Device>* devs);
+static bool GetWaveDevices(bool input, std::vector<Device>* devs);
+#elif OSX
+static const int kVideoDeviceOpenAttempts = 3;
+static const UInt32 kAudioDeviceNameLength = 64;
+// Obj-C functions defined in devicemanager-mac.mm
+extern void* CreateDeviceWatcherCallback(DeviceManager* dm);
+extern void ReleaseDeviceWatcherCallback(void* impl);
+extern bool GetQTKitVideoDevices(std::vector<Device>* out);
+static bool GetAudioDeviceIDs(bool inputs, std::vector<AudioDeviceID>* out);
+static bool GetAudioDeviceName(AudioDeviceID id, bool input, std::string* out);
+#endif
+
+DeviceManager::DeviceManager()
+    : initialized_(false),
+#if defined(WIN32)
+      need_couninitialize_(false),
+#endif
+      watcher_(new DeviceWatcher(this))
+#ifdef LINUX
+      , sound_system_(new PlatformSoundSystemFactory())
+#endif
+    {
+}
+
+DeviceManager::~DeviceManager() {
+  if (initialized_) {
+    Terminate();
+  }
+  delete watcher_;
+}
+
+bool DeviceManager::Init() {
+  if (!initialized_) {
+#if defined(WIN32)
+    HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
+    need_couninitialize_ = SUCCEEDED(hr);
+    if (FAILED(hr)) {
+      LOG(LS_ERROR) << "CoInitialize failed, hr=" << hr;
+      if (hr != RPC_E_CHANGED_MODE) {
+        return false;
+      }
+    }
+#endif
+    if (!watcher_->Start()) {
+      return false;
+    }
+    initialized_ = true;
+  }
+  return true;
+}
+
+void DeviceManager::Terminate() {
+  if (initialized_) {
+    watcher_->Stop();
+#if defined(WIN32)
+    if (need_couninitialize_) {
+      CoUninitialize();
+      need_couninitialize_ = false;
+    }
+#endif
+    initialized_ = false;
+  }
+}
+
+int DeviceManager::GetCapabilities() {
+  std::vector<Device> devices;
+  int caps = MediaEngine::VIDEO_RECV;
+  if (GetAudioInputDevices(&devices) && !devices.empty()) {
+    caps |= MediaEngine::AUDIO_SEND;
+  }
+  if (GetAudioOutputDevices(&devices) && !devices.empty()) {
+    caps |= MediaEngine::AUDIO_RECV;
+  }
+  if (GetVideoCaptureDevices(&devices) && !devices.empty()) {
+    caps |= MediaEngine::VIDEO_SEND;
+  }
+  return caps;
+}
+
+bool DeviceManager::GetAudioInputDevices(std::vector<Device>* devices) {
+  return GetAudioDevicesByPlatform(true, devices);
+}
+
+bool DeviceManager::GetAudioOutputDevices(std::vector<Device>* devices) {
+  return GetAudioDevicesByPlatform(false, devices);
+}
+
+bool DeviceManager::GetAudioInputDevice(const std::string& name, Device* out) {
+  return GetAudioDevice(true, name, out);
+}
+
+bool DeviceManager::GetAudioOutputDevice(const std::string& name, Device* out) {
+  return GetAudioDevice(false, name, out);
+}
+
+#ifdef OSX
+static bool FilterDevice(const Device& d) {
+  return ShouldDeviceBeIgnored(d.name);
+}
+#endif
+
+bool DeviceManager::GetVideoCaptureDevices(std::vector<Device>* devices) {
+  devices->clear();
+#ifdef OSX
+  if (GetQTKitVideoDevices(devices)) {
+    // Now filter out any known incompatible devices
+    devices->erase(remove_if(devices->begin(), devices->end(), FilterDevice),
+                   devices->end());
+    return true;
+  }
+  return false;
+#else
+  return GetVideoDevices(devices);
+#endif
+}
+
+bool DeviceManager::GetDefaultVideoCaptureDevice(Device* device) {
+  bool ret = false;
+#if WIN32
+  // If there are multiple capture devices, we want the first USB one.
+  // This avoids issues with defaulting to virtual cameras or grabber cards.
+  std::vector<Device> devices;
+  ret = (GetVideoDevices(&devices) && !devices.empty());
+  if (ret) {
+    *device = devices[0];
+    for (size_t i = 0; i < devices.size(); ++i) {
+      if (strnicmp(devices[i].id.c_str(), kUsbDevicePathPrefix,
+                   ARRAY_SIZE(kUsbDevicePathPrefix) - 1) == 0) {
+        *device = devices[i];
+        break;
+      }
+    }
+  }
+#else
+  // We just return the first device.
+  std::vector<Device> devices;
+  ret = (GetVideoCaptureDevices(&devices) && !devices.empty());
+  if (ret) {
+    *device = devices[0];
+  }
+#endif
+  return ret;
+}
+
+bool DeviceManager::GetVideoCaptureDevice(const std::string& name,
+                                          Device* out) {
+  // If the name is empty, return the default device.
+  if (name.empty() || name == kDefaultDeviceName) {
+    return GetDefaultVideoCaptureDevice(out);
+  }
+
+  std::vector<Device> devices;
+  if (!GetVideoCaptureDevices(&devices)) {
+    return false;
+  }
+
+  for (std::vector<Device>::const_iterator it = devices.begin();
+      it != devices.end(); ++it) {
+    if (name == it->name) {
+      *out = *it;
+      return true;
+    }
+  }
+
+  return false;
+}
+
+bool DeviceManager::GetAudioDevice(bool is_input, const std::string& name,
+                                   Device* out) {
+  // If the name is empty, return the default device id.
+  if (name.empty() || name == kDefaultDeviceName) {
+    *out = Device(name, -1);
+    return true;
+  }
+
+  std::vector<Device> devices;
+  bool ret = is_input ? GetAudioInputDevices(&devices) :
+                        GetAudioOutputDevices(&devices);
+  if (ret) {
+    ret = false;
+    for (size_t i = 0; i < devices.size(); ++i) {
+      if (devices[i].name == name) {
+        *out = devices[i];
+        ret = true;
+        break;
+      }
+    }
+  }
+  return ret;
+}
+
+bool DeviceManager::GetAudioDevicesByPlatform(bool input,
+                                              std::vector<Device>* devs) {
+  devs->clear();
+
+#if defined(LINUX)
+  if (!sound_system_.get()) {
+    return false;
+  }
+  SoundSystemInterface::SoundDeviceLocatorList list;
+  bool success;
+  if (input) {
+    success = sound_system_->EnumerateCaptureDevices(&list);
+  } else {
+    success = sound_system_->EnumeratePlaybackDevices(&list);
+  }
+  if (!success) {
+    LOG(LS_ERROR) << "Can't enumerate devices";
+    sound_system_.release();
+    return false;
+  }
+  int index = 0;
+  for (SoundSystemInterface::SoundDeviceLocatorList::iterator i = list.begin();
+       i != list.end();
+       ++i, ++index) {
+    devs->push_back(Device((*i)->name(), index));
+  }
+  SoundSystemInterface::ClearSoundDeviceLocatorList(&list);
+  sound_system_.release();
+  return true;
+
+#elif defined(WIN32)
+  if (talk_base::IsWindowsVistaOrLater()) {
+    return GetCoreAudioDevices(input, devs);
+  } else {
+    return GetWaveDevices(input, devs);
+  }
+
+#elif defined(OSX)
+  std::vector<AudioDeviceID> dev_ids;
+  bool ret = GetAudioDeviceIDs(input, &dev_ids);
+  if (ret) {
+    for (size_t i = 0; i < dev_ids.size(); ++i) {
+      std::string name;
+      if (GetAudioDeviceName(dev_ids[i], input, &name)) {
+        devs->push_back(Device(name, dev_ids[i]));
+      }
+    }
+  }
+  return ret;
+
+#else
+  return false;
+#endif
+}
+
+#if defined(WIN32)
+bool GetVideoDevices(std::vector<Device>* devices) {
+  return GetDevices(CLSID_VideoInputDeviceCategory, devices);
+}
+
+bool GetDevices(const CLSID& catid, std::vector<Device>* devices) {
+  HRESULT hr;
+
+  // CComPtr is a scoped pointer that will be auto released when going
+  // out of scope. CoUninitialize must not be called before the
+  // release.
+  CComPtr<ICreateDevEnum> sys_dev_enum;
+  CComPtr<IEnumMoniker> cam_enum;
+  if (FAILED(hr = sys_dev_enum.CoCreateInstance(CLSID_SystemDeviceEnum)) ||
+      FAILED(hr = sys_dev_enum->CreateClassEnumerator(catid, &cam_enum, 0))) {
+    LOG(LS_ERROR) << "Failed to create device enumerator, hr="  << hr;
+    return false;
+  }
+
+  // Only enum devices if CreateClassEnumerator returns S_OK. If there are no
+  // devices available, S_FALSE will be returned, but enumMk will be NULL.
+  if (hr == S_OK) {
+    CComPtr<IMoniker> mk;
+    while (cam_enum->Next(1, &mk, NULL) == S_OK) {
+      CComPtr<IPropertyBag> bag;
+      if (SUCCEEDED(mk->BindToStorage(NULL, NULL,
+          __uuidof(bag), reinterpret_cast<void**>(&bag)))) {
+        CComVariant name, path;
+        std::string name_str, path_str;
+        if (SUCCEEDED(bag->Read(kFriendlyName, &name, 0)) &&
+            name.vt == VT_BSTR) {
+          name_str = talk_base::ToUtf8(name.bstrVal);
+          if (!ShouldDeviceBeIgnored(name_str)) {
+            // Get the device id if one exists.
+            if (SUCCEEDED(bag->Read(kDevicePath, &path, 0)) &&
+                path.vt == VT_BSTR) {
+              path_str = talk_base::ToUtf8(path.bstrVal);
+            }
+
+            devices->push_back(Device(name_str, path_str));
+          }
+        }
+      }
+      mk = NULL;
+    }
+  }
+
+  return true;
+}
+
+HRESULT GetStringProp(IPropertyStore* bag, PROPERTYKEY key, std::string* out) {
+  out->clear();
+  PROPVARIANT var;
+  PropVariantInit(&var);
+
+  HRESULT hr = bag->GetValue(key, &var);
+  if (SUCCEEDED(hr)) {
+    if (var.pwszVal)
+      *out = talk_base::ToUtf8(var.pwszVal);
+    else
+      hr = E_FAIL;
+  }
+
+  PropVariantClear(&var);
+  return hr;
+}
+
+// Adapted from http://msdn.microsoft.com/en-us/library/dd370812(v=VS.85).aspx
+HRESULT CricketDeviceFromImmDevice(IMMDevice* device, Device* out) {
+  CComPtr<IPropertyStore> props;
+
+  HRESULT hr = device->OpenPropertyStore(STGM_READ, &props);
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  // Get the endpoint's name and id.
+  std::string name, guid;
+  hr = GetStringProp(props, PKEY_Device_FriendlyName, &name);
+  if (SUCCEEDED(hr)) {
+    hr = GetStringProp(props, PKEY_AudioEndpoint_GUID, &guid);
+
+    if (SUCCEEDED(hr)) {
+      out->name = name;
+      out->id = guid;
+    }
+  }
+  return hr;
+}
+
+bool GetCoreAudioDevices(bool input, std::vector<Device>* devs) {
+  HRESULT hr = S_OK;
+  CComPtr<IMMDeviceEnumerator> enumerator;
+
+  hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL,
+      __uuidof(IMMDeviceEnumerator), reinterpret_cast<void**>(&enumerator));
+  if (SUCCEEDED(hr)) {
+    CComPtr<IMMDeviceCollection> devices;
+    hr = enumerator->EnumAudioEndpoints((input ? eCapture : eRender),
+                                        DEVICE_STATE_ACTIVE, &devices);
+    if (SUCCEEDED(hr)) {
+      unsigned int count;
+      hr = devices->GetCount(&count);
+
+      if (SUCCEEDED(hr)) {
+        for (unsigned int i = 0; i < count; i++) {
+          CComPtr<IMMDevice> device;
+
+          // Get pointer to endpoint number i.
+          hr = devices->Item(i, &device);
+          if (FAILED(hr)) {
+            break;
+          }
+
+          Device dev;
+          hr = CricketDeviceFromImmDevice(device, &dev);
+          if (SUCCEEDED(hr)) {
+            devs->push_back(dev);
+          } else {
+            LOG(LS_WARNING) << "Unable to query IMM Device, skipping.  HR="
+                            << hr;
+            hr = S_FALSE;
+          }
+        }
+      }
+    }
+  }
+
+  if (!SUCCEEDED(hr)) {
+    LOG(LS_WARNING) << "GetCoreAudioDevices failed with hr " << hr;
+    return false;
+  }
+  return true;
+}
+
+bool GetWaveDevices(bool input, std::vector<Device>* devs) {
+  // Note, we don't use the System Device Enumerator interface here since it
+  // adds lots of pseudo-devices to the list, such as DirectSound and Wave
+  // variants of the same device.
+  if (input) {
+    int num_devs = waveInGetNumDevs();
+    for (int i = 0; i < num_devs; ++i) {
+      WAVEINCAPS caps;
+      if (waveInGetDevCaps(i, &caps, sizeof(caps)) == MMSYSERR_NOERROR &&
+          caps.wChannels > 0) {
+        devs->push_back(Device(talk_base::ToUtf8(caps.szPname),
+                               talk_base::ToString(i)));
+      }
+    }
+  } else {
+    int num_devs = waveOutGetNumDevs();
+    for (int i = 0; i < num_devs; ++i) {
+      WAVEOUTCAPS caps;
+      if (waveOutGetDevCaps(i, &caps, sizeof(caps)) == MMSYSERR_NOERROR &&
+          caps.wChannels > 0) {
+        devs->push_back(Device(talk_base::ToUtf8(caps.szPname), i));
+      }
+    }
+  }
+  return true;
+}
+
+DeviceWatcher::DeviceWatcher(DeviceManager* manager)
+    : manager_(manager), audio_notify_(NULL), video_notify_(NULL) {
+}
+
+bool DeviceWatcher::Start() {
+  if (!Create(NULL, _T("libjingle DeviceWatcher Window"),
+              0, 0, 0, 0, 0, 0)) {
+    return false;
+  }
+
+  audio_notify_ = Register(KSCATEGORY_AUDIO);
+  if (!audio_notify_) {
+    Stop();
+    return false;
+  }
+
+  video_notify_ = Register(KSCATEGORY_VIDEO);
+  if (!video_notify_) {
+    Stop();
+    return false;
+  }
+
+  return true;
+}
+
+void DeviceWatcher::Stop() {
+  UnregisterDeviceNotification(video_notify_);
+  video_notify_ = NULL;
+  UnregisterDeviceNotification(audio_notify_);
+  audio_notify_ = NULL;
+  Destroy();
+}
+
+HDEVNOTIFY DeviceWatcher::Register(REFGUID guid) {
+  DEV_BROADCAST_DEVICEINTERFACE dbdi;
+  dbdi.dbcc_size = sizeof(dbdi);
+  dbdi.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
+  dbdi.dbcc_classguid = guid;
+  dbdi.dbcc_name[0] = '\0';
+  return RegisterDeviceNotification(handle(), &dbdi,
+                                    DEVICE_NOTIFY_WINDOW_HANDLE);
+}
+
+void DeviceWatcher::Unregister(HDEVNOTIFY handle) {
+  UnregisterDeviceNotification(handle);
+}
+
+bool DeviceWatcher::OnMessage(UINT uMsg, WPARAM wParam, LPARAM lParam,
+                              LRESULT& result) {
+  if (uMsg == WM_DEVICECHANGE) {
+    if (wParam == DBT_DEVICEARRIVAL ||
+        wParam == DBT_DEVICEREMOVECOMPLETE) {
+      DEV_BROADCAST_DEVICEINTERFACE* dbdi =
+          reinterpret_cast<DEV_BROADCAST_DEVICEINTERFACE*>(lParam);
+      if (dbdi->dbcc_classguid == KSCATEGORY_AUDIO ||
+        dbdi->dbcc_classguid == KSCATEGORY_VIDEO) {
+        manager_->OnDevicesChange();
+      }
+    }
+    result = 0;
+    return true;
+  }
+
+  return false;
+}
+#elif defined(OSX)
+static bool GetAudioDeviceIDs(bool input,
+                              std::vector<AudioDeviceID>* out_dev_ids) {
+  UInt32 propsize;
+  OSErr err = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices,
+                                           &propsize, NULL);
+  if (0 != err) {
+    LOG(LS_ERROR) << "Couldn't get information about property, "
+                  << "so no device list acquired.";
+    return false;
+  }
+
+  size_t num_devices = propsize / sizeof(AudioDeviceID);
+  talk_base::scoped_array<AudioDeviceID> device_ids(
+      new AudioDeviceID[num_devices]);
+
+  err = AudioHardwareGetProperty(kAudioHardwarePropertyDevices,
+                                 &propsize, device_ids.get());
+  if (0 != err) {
+    LOG(LS_ERROR) << "Failed to get device ids, "
+                  << "so no device listing acquired.";
+    return false;
+  }
+
+  for (size_t i = 0; i < num_devices; ++i) {
+    AudioDeviceID an_id = device_ids[i];
+    // find out the number of channels for this direction
+    // (input/output) on this device -
+    // we'll ignore anything with no channels.
+    err = AudioDeviceGetPropertyInfo(an_id, 0, input,
+                                     kAudioDevicePropertyStreams,
+                                     &propsize, NULL);
+    if (0 == err) {
+      unsigned num_channels = propsize / sizeof(AudioStreamID);
+      if (0 < num_channels) {
+        out_dev_ids->push_back(an_id);
+      }
+    } else {
+      LOG(LS_ERROR) << "No property info for stream property for device id "
+                    << an_id << "(is_input == " << input
+                    << "), so not including it in the list.";
+    }
+  }
+
+  return true;
+}
+
+static bool GetAudioDeviceName(AudioDeviceID id,
+                               bool input,
+                               std::string* out_name) {
+  UInt32 nameLength = kAudioDeviceNameLength;
+  char name[kAudioDeviceNameLength + 1];
+  OSErr err = AudioDeviceGetProperty(id, 0, input,
+                                     kAudioDevicePropertyDeviceName,
+                                     &nameLength, name);
+  if (0 != err) {
+    LOG(LS_ERROR) << "No name acquired for device id " << id;
+    return false;
+  }
+
+  *out_name = name;
+  return true;
+}
+
+DeviceWatcher::DeviceWatcher(DeviceManager* manager)
+    : manager_(manager), impl_(NULL) {
+}
+
+bool DeviceWatcher::Start() {
+  if (!impl_) {
+    impl_ = CreateDeviceWatcherCallback(manager_);
+  }
+  return impl_ != NULL;
+}
+
+void DeviceWatcher::Stop() {
+  if (impl_) {
+    ReleaseDeviceWatcherCallback(impl_);
+    impl_ = NULL;
+  }
+}
+
+#elif defined(LINUX)
+static const std::string kVideoMetaPathK2_4("/proc/video/dev/");
+static const std::string kVideoMetaPathK2_6("/sys/class/video4linux/");
+
+enum MetaType { M2_4, M2_6, NONE };
+
+static void ScanDeviceDirectory(const std::string& devdir,
+                                std::vector<Device>* devices) {
+  talk_base::scoped_ptr<talk_base::DirectoryIterator> directoryIterator(
+      talk_base::Filesystem::IterateDirectory());
+
+  if (directoryIterator->Iterate(talk_base::Pathname(devdir))) {
+    do {
+      std::string filename = directoryIterator->Name();
+      std::string device_name = devdir + filename;
+      if (!directoryIterator->IsDots()) {
+        if (filename.find("video") == 0 &&
+            V4LLookup::IsV4L2Device(device_name)) {
+          devices->push_back(Device(device_name, device_name));
+        }
+      }
+    } while (directoryIterator->Next());
+  }
+}
+
+static std::string GetVideoDeviceNameK2_6(const std::string& device_meta_path) {
+  std::string device_name;
+
+  talk_base::scoped_ptr<talk_base::FileStream> device_meta_stream(
+      talk_base::Filesystem::OpenFile(device_meta_path, "r"));
+
+  if (device_meta_stream.get() != NULL) {
+    if (device_meta_stream->ReadLine(&device_name) != talk_base::SR_SUCCESS) {
+      LOG(LS_ERROR) << "Failed to read V4L2 device meta " << device_meta_path;
+    }
+    device_meta_stream->Close();
+  }
+
+  return device_name;
+}
+
+static std::string Trim(const std::string& s, const std::string& drop = " \t") {
+  std::string::size_type first = s.find_first_not_of(drop);
+  std::string::size_type last  = s.find_last_not_of(drop);
+
+  if (first == std::string::npos || last == std::string::npos)
+    return std::string("");
+
+  return s.substr(first, last - first + 1);
+}
+
+static std::string GetVideoDeviceNameK2_4(const std::string& device_meta_path) {
+  talk_base::ConfigParser::MapVector all_values;
+
+  talk_base::ConfigParser config_parser;
+  talk_base::FileStream* file_stream =
+      talk_base::Filesystem::OpenFile(device_meta_path, "r");
+
+  if (file_stream == NULL) return "";
+
+  config_parser.Attach(file_stream);
+  config_parser.Parse(&all_values);
+
+  for (talk_base::ConfigParser::MapVector::iterator i = all_values.begin();
+      i != all_values.end(); ++i) {
+    talk_base::ConfigParser::SimpleMap::iterator device_name_i =
+        i->find("name");
+
+    if (device_name_i != i->end()) {
+      return device_name_i->second;
+    }
+  }
+
+  return "";
+}
+
+static std::string GetVideoDeviceName(MetaType meta,
+    const std::string& device_file_name) {
+  std::string device_meta_path;
+  std::string device_name;
+  std::string meta_file_path;
+
+  if (meta == M2_6) {
+    meta_file_path = kVideoMetaPathK2_6 + device_file_name + "/name";
+
+    LOG(LS_INFO) << "Trying " + meta_file_path;
+    device_name = GetVideoDeviceNameK2_6(meta_file_path);
+
+    if (device_name.empty()) {
+      meta_file_path = kVideoMetaPathK2_6 + device_file_name + "/model";
+
+      LOG(LS_INFO) << "Trying " << meta_file_path;
+      device_name = GetVideoDeviceNameK2_6(meta_file_path);
+    }
+  } else {
+    meta_file_path = kVideoMetaPathK2_4 + device_file_name;
+    LOG(LS_INFO) << "Trying " << meta_file_path;
+    device_name = GetVideoDeviceNameK2_4(meta_file_path);
+  }
+
+  if (device_name.empty()) {
+    device_name = "/dev/" + device_file_name;
+    LOG(LS_ERROR)
+      << "Device name not found, defaulting to device path " << device_name;
+  }
+
+  LOG(LS_INFO) << "Name for " << device_file_name << " is " << device_name;
+
+  return Trim(device_name);
+}
+
+static void ScanV4L2Devices(std::vector<Device>* devices) {
+  LOG(LS_INFO) << ("Enumerating V4L2 devices");
+
+  MetaType meta;
+  std::string metadata_dir;
+
+  talk_base::scoped_ptr<talk_base::DirectoryIterator> directoryIterator(
+      talk_base::Filesystem::IterateDirectory());
+
+  // Try and guess kernel version
+  if (directoryIterator->Iterate(kVideoMetaPathK2_6)) {
+    meta = M2_6;
+    metadata_dir = kVideoMetaPathK2_6;
+  } else if (directoryIterator->Iterate(kVideoMetaPathK2_4)) {
+    meta = M2_4;
+    metadata_dir = kVideoMetaPathK2_4;
+  } else {
+    meta = NONE;
+  }
+
+  if (meta != NONE) {
+    LOG(LS_INFO) << "V4L2 device metadata found at " << metadata_dir;
+
+    do {
+      std::string filename = directoryIterator->Name();
+
+      if (filename.find("video") == 0) {
+        std::string device_path = "/dev/" + filename;
+
+        if (V4LLookup::IsV4L2Device(device_path)) {
+          devices->push_back(
+              Device(GetVideoDeviceName(meta, filename), device_path));
+        }
+      }
+    } while (directoryIterator->Next());
+  } else {
+    LOG(LS_ERROR) << "Unable to detect v4l2 metadata directory";
+  }
+
+  if (devices->size() == 0) {
+    LOG(LS_INFO) << "Plan B. Scanning all video devices in /dev directory";
+    ScanDeviceDirectory("/dev/", devices);
+  }
+
+  LOG(LS_INFO) << "Total V4L2 devices found : " << devices->size();
+}
+
+static bool GetVideoDevices(std::vector<Device>* devices) {
+  ScanV4L2Devices(devices);
+  return true;
+}
+
+DeviceWatcher::DeviceWatcher(DeviceManager* dm)
+    : manager_(dm), udev_(NULL), udev_monitor_(NULL), registered_(false) {}
+
+bool DeviceWatcher::Start() {
+  // We deliberately return true in the failure paths here because libudev is
+  // not a critical component of a Linux system so it may not be present/usable,
+  // and we don't want to halt DeviceManager initialization in such a case.
+  if (!libudev_.Load()) {
+    LOG(LS_WARNING) << "libudev not present/usable; DeviceWatcher disabled";
+    return true;
+  }
+  udev_ = LATE(udev_new)();
+  if (!udev_) {
+    LOG_ERR(LS_ERROR) << "udev_new()";
+    return true;
+  }
+  // The second argument here is the event source. It can be either "kernel" or
+  // "udev", but "udev" is the only correct choice. Apps listen on udev and the
+  // udev daemon in turn listens on the kernel.
+  udev_monitor_ = LATE(udev_monitor_new_from_netlink)(udev_, "udev");
+  if (!udev_monitor_) {
+    LOG_ERR(LS_ERROR) << "udev_monitor_new_from_netlink()";
+    return true;
+  }
+  // We only listen for changes in the video devices. Audio devices are more or
+  // less unimportant because receiving device change notifications really only
+  // matters for broadcasting updated send/recv capabilities based on whether
+  // there is at least one device available, and almost all computers have at
+  // least one audio device. Also, PulseAudio device notifications don't come
+  // from the udev daemon, they come from the PulseAudio daemon, so we'd only
+  // want to listen for audio device changes from udev if using ALSA. For
+  // simplicity, we don't bother with any audio stuff at all.
+  if (LATE(udev_monitor_filter_add_match_subsystem_devtype)(udev_monitor_,
+                                                            "video4linux",
+                                                            NULL) < 0) {
+    LOG_ERR(LS_ERROR) << "udev_monitor_filter_add_match_subsystem_devtype()";
+    return true;
+  }
+  if (LATE(udev_monitor_enable_receiving)(udev_monitor_) < 0) {
+    LOG_ERR(LS_ERROR) << "udev_monitor_enable_receiving()";
+    return true;
+  }
+  static_cast<talk_base::PhysicalSocketServer*>(
+      talk_base::Thread::Current()->socketserver())->Add(this);
+  registered_ = true;
+  return true;
+}
+
+void DeviceWatcher::Stop() {
+  if (registered_) {
+    static_cast<talk_base::PhysicalSocketServer*>(
+        talk_base::Thread::Current()->socketserver())->Remove(this);
+    registered_ = false;
+  }
+  if (udev_monitor_) {
+    LATE(udev_monitor_unref)(udev_monitor_);
+    udev_monitor_ = NULL;
+  }
+  if (udev_) {
+    LATE(udev_unref)(udev_);
+    udev_ = NULL;
+  }
+  libudev_.Unload();
+}
+
+uint32 DeviceWatcher::GetRequestedEvents() {
+  return talk_base::DE_READ;
+}
+
+void DeviceWatcher::OnPreEvent(uint32 ff) {
+  // Nothing to do.
+}
+
+void DeviceWatcher::OnEvent(uint32 ff, int err) {
+  udev_device* device = LATE(udev_monitor_receive_device)(udev_monitor_);
+  if (!device) {
+    // Probably the socket connection to the udev daemon was terminated (perhaps
+    // the daemon crashed or is being restarted?).
+    LOG_ERR(LS_WARNING) << "udev_monitor_receive_device()";
+    // Stop listening to avoid potential livelock (an fd with EOF in it is
+    // always considered readable).
+    static_cast<talk_base::PhysicalSocketServer*>(
+        talk_base::Thread::Current()->socketserver())->Remove(this);
+    registered_ = false;
+    return;
+  }
+  // Else we read the device successfully.
+
+  // Since we already have our own filesystem-based device enumeration code, we
+  // simply re-enumerate rather than inspecting the device event.
+  LATE(udev_device_unref)(device);
+  manager_->OnDevicesChange();
+}
+
+int DeviceWatcher::GetDescriptor() {
+  return LATE(udev_monitor_get_fd)(udev_monitor_);
+}
+
+bool DeviceWatcher::IsDescriptorClosed() {
+  // If it is closed then we will just get an error in
+  // udev_monitor_receive_device and unregister, so we don't need to check for
+  // it separately.
+  return false;
+}
+
+#endif
+
+// TODO: Try to get hold of a copy of Final Cut to understand why we
+//               crash while scanning their components on OS X.
+#if !defined(LINUX) && !defined(IOS)
+static bool ShouldDeviceBeIgnored(const std::string& device_name) {
+  static const char* const kFilteredDevices[] =  {
+      "Google Camera Adapter",   // Our own magiccams
+#ifdef WIN32
+      "Asus virtual Camera",     // Bad Asus desktop virtual cam
+      "Bluetooth Video",         // Bad Sony viao bluetooth sharing driver
+#elif OSX
+      "DVCPRO HD",               // Final cut
+      "Sonix SN9C201p",          // Crashes in OpenAComponent and CloseComponent
+#endif
+  };
+
+  for (int i = 0; i < ARRAY_SIZE(kFilteredDevices); ++i) {
+    if (strnicmp(device_name.c_str(), kFilteredDevices[i],
+        strlen(kFilteredDevices[i])) == 0) {
+      LOG(LS_INFO) << "Ignoring device " << device_name;
+      return true;
+    }
+  }
+  return false;
+}
+#endif
+
+};  // namespace cricket
diff --git a/talk/session/phone/devicemanager.h b/talk/session/phone/devicemanager.h
new file mode 100644
index 0000000..cd41f6f
--- /dev/null
+++ b/talk/session/phone/devicemanager.h
@@ -0,0 +1,109 @@
+/*
+ * 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 TALK_SESSION_PHONE_DEVICEMANAGER_H_
+#define TALK_SESSION_PHONE_DEVICEMANAGER_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/sigslot.h"
+#include "talk/base/stringencode.h"
+#ifdef LINUX
+#include "talk/sound/soundsystemfactory.h"
+#endif
+
+namespace cricket {
+
+class DeviceWatcher;
+
+// Used to represent an audio or video capture or render device.
+struct Device {
+  Device() {}
+  Device(const std::string& first, int second)
+      : name(first),
+        id(talk_base::ToString(second)) {
+  }
+  Device(const std::string& first, const std::string& second)
+      : name(first), id(second) {}
+
+  std::string name;
+  std::string id;
+};
+
+// DeviceManager manages the audio and video devices on the system.
+// Methods are virtual to allow for easy stubbing/mocking in tests.
+class DeviceManager {
+ public:
+  DeviceManager();
+  virtual ~DeviceManager();
+
+  // Initialization
+  virtual bool Init();
+  virtual void Terminate();
+  bool initialized() const { return initialized_; }
+
+  // Capabilities
+  virtual int GetCapabilities();
+
+  // Device enumeration
+  virtual bool GetAudioInputDevices(std::vector<Device>* devices);
+  virtual bool GetAudioOutputDevices(std::vector<Device>* devices);
+
+  bool GetAudioInputDevice(const std::string& name, Device* out);
+  bool GetAudioOutputDevice(const std::string& name, Device* out);
+
+  virtual bool GetVideoCaptureDevices(std::vector<Device>* devs);
+  bool GetVideoCaptureDevice(const std::string& name, Device* out);
+
+  sigslot::signal0<> SignalDevicesChange;
+
+  void OnDevicesChange() { SignalDevicesChange(); }
+
+  static const std::string kDefaultDeviceName;
+
+ protected:
+  virtual bool GetAudioDevice(bool is_input, const std::string& name,
+                              Device* out);
+  virtual bool GetDefaultVideoCaptureDevice(Device* device);
+
+ private:
+  bool GetAudioDevicesByPlatform(bool input, std::vector<Device>* devs);
+
+  bool initialized_;
+#ifdef WIN32
+  bool need_couninitialize_;
+#endif
+  DeviceWatcher* watcher_;
+#ifdef LINUX
+  SoundSystemHandle sound_system_;
+#endif
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_DEVICEMANAGER_H_
diff --git a/talk/session/phone/devicemanager_mac.mm b/talk/session/phone/devicemanager_mac.mm
new file mode 100644
index 0000000..3537ed9
--- /dev/null
+++ b/talk/session/phone/devicemanager_mac.mm
@@ -0,0 +1,109 @@
+/*
+ * libjingle
+ * Copyright 2004--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 "talk/session/phone/devicemanager.h"
+
+#import <QTKit/QTKit.h>
+
+#include "talk/base/logging.h"
+
+@interface DeviceWatcherImpl : NSObject {
+@private
+  cricket::DeviceManager* manager_;
+}
+- (id)init:(cricket::DeviceManager*) dm;
+- (void)onDevicesChanged:(NSNotification *)notification;
+@end
+
+@implementation DeviceWatcherImpl
+- (id)init:(cricket::DeviceManager*) dm {
+  if ((self = [super init])) {
+    manager_ = dm;
+    [[NSNotificationCenter defaultCenter] addObserver:self
+        selector:@selector(onDevicesChanged:)
+        name:QTCaptureDeviceWasConnectedNotification
+        object:nil];
+    [[NSNotificationCenter defaultCenter] addObserver:self
+        selector:@selector(onDevicesChanged:)
+        name:QTCaptureDeviceWasDisconnectedNotification
+        object:nil];
+  }
+  return self;
+}
+- (void)dealloc {
+  [[NSNotificationCenter defaultCenter] removeObserver:self];
+  [super dealloc];
+}
+- (void)onDevicesChanged:(NSNotification *)notification {
+  manager_->OnDevicesChange();
+}
+@end
+
+namespace cricket {
+
+void* CreateDeviceWatcherCallback(DeviceManager* dm) {
+  return [[DeviceWatcherImpl alloc] init:dm];
+}
+void ReleaseDeviceWatcherCallback(void* watcher) {
+  DeviceWatcherImpl* watcher_impl = static_cast<DeviceWatcherImpl*>(watcher);
+  [watcher_impl release];
+}
+
+bool GetQTKitVideoDevices(std::vector<Device>* devices) {
+  NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
+
+  NSArray* qt_capture_devices =
+      [QTCaptureDevice inputDevicesWithMediaType:QTMediaTypeVideo];
+  NSUInteger count = [qt_capture_devices count];
+  LOG(LS_INFO) << count << " capture device(s) found:";
+  for (NSUInteger i = 0; i < count; ++i) {
+    QTCaptureDevice* qt_capture_device = [qt_capture_devices objectAtIndex:i];
+
+    static const NSString* kFormat = @"localizedDisplayName: \"%@\", "
+        "modelUniqueID: \"%@\", uniqueID \"%@\", isConnected: %d, isOpen: %d, "
+        "isInUseByAnotherApplication: %d";
+    NSString* info = [NSString stringWithFormat:kFormat,
+        [qt_capture_device localizedDisplayName],
+        [qt_capture_device modelUniqueID],
+        [qt_capture_device uniqueID],
+        [qt_capture_device isConnected],
+        [qt_capture_device isOpen],
+        [qt_capture_device isInUseByAnotherApplication]];
+    LOG(LS_INFO) << [info cStringUsingEncoding:NSUTF8StringEncoding];
+
+    std::string name([[qt_capture_device localizedDisplayName]
+                         cStringUsingEncoding:NSUTF8StringEncoding]);
+    devices->push_back(Device(name,
+       [[qt_capture_device uniqueID]
+           cStringUsingEncoding:NSUTF8StringEncoding]));
+  }
+
+  [pool drain];
+  return true;
+}
+
+}  // namespace cricket
diff --git a/talk/session/phone/filemediaengine.cc b/talk/session/phone/filemediaengine.cc
new file mode 100644
index 0000000..49d92b6
--- /dev/null
+++ b/talk/session/phone/filemediaengine.cc
@@ -0,0 +1,252 @@
+// 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/session/phone/filemediaengine.h"
+
+#include <climits>
+
+#include "talk/base/buffer.h"
+#include "talk/base/event.h"
+#include "talk/base/logging.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/stream.h"
+#include "talk/session/phone/rtpdump.h"
+
+namespace cricket {
+
+///////////////////////////////////////////////////////////////////////////
+// Implementation of FileMediaEngine.
+///////////////////////////////////////////////////////////////////////////
+int FileMediaEngine::GetCapabilities() {
+  int capabilities = 0;
+  if (!voice_input_filename_.empty()) {
+    capabilities |= MediaEngine::AUDIO_SEND;
+  }
+  if (!voice_output_filename_.empty()) {
+    capabilities |= MediaEngine::AUDIO_RECV;
+  }
+  if (!video_input_filename_.empty()) {
+    capabilities |= MediaEngine::VIDEO_SEND;
+  }
+  if (!video_output_filename_.empty()) {
+    capabilities |= MediaEngine::VIDEO_RECV;
+  }
+  return capabilities;
+}
+
+VoiceMediaChannel* FileMediaEngine::CreateChannel() {
+  if (!voice_input_filename_.empty() || !voice_output_filename_.empty()) {
+    return new FileVoiceChannel(voice_input_filename_, voice_output_filename_);
+  } else {
+    return NULL;
+  }
+}
+
+VideoMediaChannel* FileMediaEngine::CreateVideoChannel(
+    VoiceMediaChannel* voice_ch) {
+  if (!video_input_filename_.empty() || !video_output_filename_.empty()) {
+    return new FileVideoChannel(video_input_filename_, video_output_filename_);
+  } else {
+    return NULL;
+  }
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Definition of RtpSenderReceiver.
+///////////////////////////////////////////////////////////////////////////
+class RtpSenderReceiver
+    : public talk_base::Thread, public talk_base::MessageHandler {
+ public:
+  RtpSenderReceiver(MediaChannel* channel, const std::string& in_file,
+                    const std::string& out_file);
+
+  // Called by media channel. Context: media channel thread.
+  bool SetSend(bool send);
+  void OnPacketReceived(talk_base::Buffer* packet);
+
+  // Override virtual method of parent MessageHandler. Context: Worker Thread.
+  virtual void OnMessage(talk_base::Message* pmsg);
+
+ private:
+  // Read the next RTP dump packet, whose RTP SSRC is the same as first_ssrc_.
+  // Return true if successful.
+  bool ReadNextPacket(RtpDumpPacket* packet);
+  // Send a RTP packet to the network. The input parameter data points to the
+  // start of the RTP packet and len is the packet size. Return true if the sent
+  // size is equal to len.
+  bool SendRtpPacket(const void* data, size_t len);
+
+  MediaChannel* media_channel_;
+  talk_base::scoped_ptr<talk_base::StreamInterface> input_stream_;
+  talk_base::scoped_ptr<talk_base::StreamInterface> output_stream_;
+  talk_base::scoped_ptr<RtpDumpLoopReader> rtp_dump_reader_;
+  talk_base::scoped_ptr<RtpDumpWriter> rtp_dump_writer_;
+  // RTP dump packet read from the input stream.
+  RtpDumpPacket rtp_dump_packet_;
+  uint32 start_send_time_;
+  bool sending_;
+  bool first_packet_;
+  uint32 first_ssrc_;
+
+  DISALLOW_COPY_AND_ASSIGN(RtpSenderReceiver);
+};
+
+///////////////////////////////////////////////////////////////////////////
+// Implementation of RtpSenderReceiver.
+///////////////////////////////////////////////////////////////////////////
+RtpSenderReceiver::RtpSenderReceiver(MediaChannel* channel,
+                                     const std::string& in_file,
+                                     const std::string& out_file)
+    : media_channel_(channel),
+      sending_(false),
+      first_packet_(true) {
+  input_stream_.reset(talk_base::Filesystem::OpenFile(
+      talk_base::Pathname(in_file), "rb"));
+  if (input_stream_.get()) {
+    rtp_dump_reader_.reset(new RtpDumpLoopReader(input_stream_.get()));
+    // Start the sender thread, which reads rtp dump records, waits based on
+    // the record timestamps, and sends the RTP packets to the network.
+    Thread::Start();
+  }
+
+  // Create a rtp dump writer for the output RTP dump stream.
+  output_stream_.reset(talk_base::Filesystem::OpenFile(
+      talk_base::Pathname(out_file), "wb"));
+  if (output_stream_.get()) {
+    rtp_dump_writer_.reset(new RtpDumpWriter(output_stream_.get()));
+  }
+}
+
+bool RtpSenderReceiver::SetSend(bool send) {
+  bool was_sending = sending_;
+  sending_ = send;
+  if (!was_sending && sending_) {
+    PostDelayed(0, this);  // Wake up the send thread.
+    start_send_time_ = talk_base::Time();
+  }
+  return true;
+}
+
+void RtpSenderReceiver::OnPacketReceived(talk_base::Buffer* packet) {
+  if (rtp_dump_writer_.get()) {
+    rtp_dump_writer_->WriteRtpPacket(packet->data(), packet->length());
+  }
+}
+
+void RtpSenderReceiver::OnMessage(talk_base::Message* pmsg) {
+  if (!sending_) {
+    // If the sender thread is not sending, ignore this message. The thread goes
+    // to sleep until SetSend(true) wakes it up.
+    return;
+  }
+
+  if (!first_packet_) {
+    // Send the previously read packet.
+    SendRtpPacket(&rtp_dump_packet_.data[0], rtp_dump_packet_.data.size());
+  }
+
+  if (ReadNextPacket(&rtp_dump_packet_)) {
+    int wait = talk_base::TimeUntil(
+        start_send_time_ + rtp_dump_packet_.elapsed_time);
+    wait = talk_base::_max(0, wait);
+    PostDelayed(wait, this);
+  } else {
+    Quit();
+  }
+}
+
+bool RtpSenderReceiver::ReadNextPacket(RtpDumpPacket* packet) {
+  while (talk_base::SR_SUCCESS == rtp_dump_reader_->ReadPacket(packet)) {
+    uint32 ssrc;
+    if (!packet->GetRtpSsrc(&ssrc)) {
+      return false;
+    }
+    if (first_packet_) {
+      first_packet_ = false;
+      first_ssrc_ = ssrc;
+    }
+    if (ssrc == first_ssrc_) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool RtpSenderReceiver::SendRtpPacket(const void* data, size_t len) {
+  if (!media_channel_ || !media_channel_->network_interface()) {
+    return false;
+  }
+
+  talk_base::Buffer packet(data, len, kMaxRtpPacketLen);
+  return media_channel_->network_interface()->SendPacket(&packet);
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Implementation of FileVoiceChannel.
+///////////////////////////////////////////////////////////////////////////
+FileVoiceChannel::FileVoiceChannel(const std::string& in_file,
+                                   const std::string& out_file)
+    : rtp_sender_receiver_(new RtpSenderReceiver(this, in_file, out_file)) {
+}
+
+FileVoiceChannel::~FileVoiceChannel() {}
+
+bool FileVoiceChannel::SetSendCodecs(const std::vector<AudioCodec>& codecs) {
+  // TODO: Check the format of RTP dump input.
+  return true;
+}
+
+bool FileVoiceChannel::SetSend(SendFlags flag) {
+  return rtp_sender_receiver_->SetSend(flag != SEND_NOTHING);
+}
+
+void FileVoiceChannel::OnPacketReceived(talk_base::Buffer* packet) {
+  rtp_sender_receiver_->OnPacketReceived(packet);
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Implementation of FileVideoChannel.
+///////////////////////////////////////////////////////////////////////////
+FileVideoChannel::FileVideoChannel(const std::string& in_file,
+                                   const std::string& out_file)
+    : rtp_sender_receiver_(new RtpSenderReceiver(this, in_file, out_file)) {
+}
+
+FileVideoChannel::~FileVideoChannel() {}
+
+bool FileVideoChannel::SetSendCodecs(const std::vector<VideoCodec>& codecs) {
+  // TODO: Check the format of RTP dump input.
+  return true;
+}
+
+bool FileVideoChannel::SetSend(bool send) {
+  return rtp_sender_receiver_->SetSend(send);
+}
+
+void FileVideoChannel::OnPacketReceived(talk_base::Buffer* packet) {
+  rtp_sender_receiver_->OnPacketReceived(packet);
+}
+
+}  // namespace cricket
diff --git a/talk/session/phone/filemediaengine.h b/talk/session/phone/filemediaengine.h
new file mode 100644
index 0000000..2276772
--- /dev/null
+++ b/talk/session/phone/filemediaengine.h
@@ -0,0 +1,197 @@
+// 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_SESSION_PHONE_FILEMEDIAENGINE_H_
+#define TALK_SESSION_PHONE_FILEMEDIAENGINE_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/scoped_ptr.h"
+#include "talk/session/phone/codec.h"
+#include "talk/session/phone/mediachannel.h"
+#include "talk/session/phone/mediaengine.h"
+
+namespace talk_base {
+class StreamInterface;
+}
+
+namespace cricket {
+
+// A media engine contains a capturer, an encoder, and a sender in the sender
+// side and a receiver, a decoder, and a renderer in the receiver side.
+// FileMediaEngine simulates the capturer and the encoder via an input RTP dump
+// stream and simulates the decoder and the renderer via an output RTP dump
+// stream. Depending on the parameters of the constructor, FileMediaEngine can
+// act as file voice engine, file video engine, or both. Currently, we use
+// only the RTP dump packets. TODO: Enable RTCP packets.
+class FileMediaEngine : public MediaEngine {
+ public:
+  FileMediaEngine() {}
+  virtual ~FileMediaEngine() {}
+
+  // Set the file name of the input or output RTP dump for voice or video.
+  // Should be called before the channel is created.
+  void set_voice_input_filename(const std::string& filename) {
+    voice_input_filename_ = filename;
+  }
+  void set_voice_output_filename(const std::string& filename) {
+    voice_output_filename_ = filename;
+  }
+  void set_video_input_filename(const std::string& filename) {
+    video_input_filename_ = filename;
+  }
+  void set_video_output_filename(const std::string& filename) {
+    video_output_filename_ = filename;
+  }
+
+  // Should be called before codecs() and video_codecs() are called. We need to
+  // set the voice and video codecs; otherwise, Jingle initiation will fail.
+  void set_voice_codecs(const std::vector<AudioCodec>& codecs) {
+    voice_codecs_ = codecs;
+  }
+  void set_video_codecs(const std::vector<VideoCodec>& codecs) {
+    video_codecs_ = codecs;
+  }
+
+  // Implement pure virtual methods of MediaEngine.
+  virtual bool Init() { return true; }
+  virtual void Terminate() {}
+  virtual int GetCapabilities();
+  virtual VoiceMediaChannel* CreateChannel();
+  virtual VideoMediaChannel* CreateVideoChannel(VoiceMediaChannel* voice_ch);
+  virtual SoundclipMedia* CreateSoundclip() { return NULL; }
+  virtual bool SetAudioOptions(int options) { return true; }
+  virtual bool SetVideoOptions(int options) { return true; }
+  virtual bool SetDefaultVideoEncoderConfig(const VideoEncoderConfig& config) {
+    return true;
+  }
+  virtual bool SetSoundDevices(const Device* in_dev, const Device* out_dev) {
+    return true;
+  }
+  virtual bool SetVideoCaptureDevice(const Device* cam_device) { return true; }
+  virtual bool SetOutputVolume(int level) { return true; }
+  virtual int GetInputLevel() { return 0; }
+  virtual bool SetLocalMonitor(bool enable) { return true; }
+  virtual bool SetLocalRenderer(VideoRenderer* renderer) { return true; }
+  // TODO: control channel send?
+  virtual CaptureResult SetVideoCapture(bool capture) { return CR_SUCCESS; }
+  virtual const std::vector<AudioCodec>& audio_codecs() {
+    return voice_codecs_;
+  }
+  virtual const std::vector<VideoCodec>& video_codecs() {
+    return video_codecs_;
+  }
+  virtual bool FindAudioCodec(const AudioCodec& codec) { return true; }
+  virtual bool FindVideoCodec(const VideoCodec& codec) { return true; }
+  virtual void SetVoiceLogging(int min_sev, const char* filter) {}
+  virtual void SetVideoLogging(int min_sev, const char* filter) {}
+
+ private:
+  std::string voice_input_filename_;
+  std::string voice_output_filename_;
+  std::string video_input_filename_;
+  std::string video_output_filename_;
+  std::vector<AudioCodec> voice_codecs_;
+  std::vector<VideoCodec> video_codecs_;
+
+  DISALLOW_COPY_AND_ASSIGN(FileMediaEngine);
+};
+
+class RtpSenderReceiver;  // Forward declaration. Defined in the .cc file.
+
+class FileVoiceChannel : public VoiceMediaChannel {
+ public:
+  FileVoiceChannel(const std::string& in_file, const std::string& out_file);
+  virtual ~FileVoiceChannel();
+
+  // Implement pure virtual methods of VoiceMediaChannel.
+  virtual bool SetRecvCodecs(const std::vector<AudioCodec>& codecs) {
+    return true;
+  }
+  virtual bool SetSendCodecs(const std::vector<AudioCodec>& codecs);
+  virtual bool SetPlayout(bool playout) { return true; }
+  virtual bool SetSend(SendFlags flag);
+  virtual bool AddStream(uint32 ssrc) { return true; }
+  virtual bool RemoveStream(uint32 ssrc) { return true; }
+  virtual bool GetActiveStreams(AudioInfo::StreamList* actives) { return true; }
+  virtual int GetOutputLevel() { return 0; }
+  virtual void SetRingbackTone(const char* buf, int len) {}
+  virtual bool PlayRingbackTone(bool play, bool loop) { return true; }
+  virtual bool PressDTMF(int event, bool playout) { return true; }
+  virtual bool GetStats(VoiceMediaInfo* info) { return true; }
+
+  // Implement pure virtual methods of MediaChannel.
+  virtual void OnPacketReceived(talk_base::Buffer* packet);
+  virtual void OnRtcpReceived(talk_base::Buffer* packet) {}
+  virtual void SetSendSsrc(uint32 id) {}  // TODO: change RTP packet?
+  virtual bool SetRtcpCName(const std::string& cname) { return true; }
+  virtual bool Mute(bool on) { return false; }
+  virtual bool SetSendBandwidth(bool autobw, int bps) { return true; }
+  virtual bool SetOptions(int options) { return true; }
+
+ private:
+  talk_base::scoped_ptr<RtpSenderReceiver> rtp_sender_receiver_;
+  DISALLOW_COPY_AND_ASSIGN(FileVoiceChannel);
+};
+
+class FileVideoChannel : public VideoMediaChannel {
+ public:
+  FileVideoChannel(const std::string& in_file, const std::string& out_file);
+  virtual ~FileVideoChannel();
+
+  // Implement pure virtual methods of VideoMediaChannel.
+  virtual bool SetRecvCodecs(const std::vector<VideoCodec>& codecs) {
+    return true;
+  }
+  virtual bool SetSendCodecs(const std::vector<VideoCodec>& codecs);
+  virtual bool SetRender(bool render) { return true; }
+  virtual bool SetSend(bool send);
+  virtual bool AddStream(uint32 ssrc, uint32 voice_ssrc) { return true; }
+  virtual bool RemoveStream(uint32 ssrc) { return true; }
+  virtual bool SetRenderer(uint32 ssrc, VideoRenderer* renderer) {
+    return true;
+  }
+  virtual bool GetStats(VideoMediaInfo* info) { return true; }
+  virtual bool SendIntraFrame() { return false; }
+  virtual bool RequestIntraFrame() { return false; }
+
+  // Implement pure virtual methods of MediaChannel.
+  virtual void OnPacketReceived(talk_base::Buffer* packet);
+  virtual void OnRtcpReceived(talk_base::Buffer* packet) {}
+  virtual void SetSendSsrc(uint32 id) {}  // TODO: change RTP packet?
+  virtual bool SetRtcpCName(const std::string& cname) { return true; }
+  virtual bool Mute(bool on) { return false; }
+  virtual bool SetSendBandwidth(bool autobw, int bps) { return true; }
+  virtual bool SetOptions(int options) { return true; }
+
+ private:
+  talk_base::scoped_ptr<RtpSenderReceiver> rtp_sender_receiver_;
+  DISALLOW_COPY_AND_ASSIGN(FileVideoChannel);
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_FILEMEDIAENGINE_H_
diff --git a/talk/session/phone/libudevsymboltable.cc b/talk/session/phone/libudevsymboltable.cc
new file mode 100644
index 0000000..b312306
--- /dev/null
+++ b/talk/session/phone/libudevsymboltable.cc
@@ -0,0 +1,39 @@
+/*
+ * libjingle
+ * Copyright 2004--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 "talk/session/phone/libudevsymboltable.h"
+
+namespace cricket {
+
+LATE_BINDING_SYMBOL_TABLE_DEFINE_BEGIN(LibUDevSymbolTable, "libudev.so.0")
+#define X(sym) \
+    LATE_BINDING_SYMBOL_TABLE_DEFINE_ENTRY(LibUDevSymbolTable, sym)
+LIBUDEV_SYMBOLS_LIST
+#undef X
+LATE_BINDING_SYMBOL_TABLE_DEFINE_END(LibUDevSymbolTable)
+
+}  // namespace cricket
diff --git a/talk/session/phone/libudevsymboltable.h b/talk/session/phone/libudevsymboltable.h
new file mode 100644
index 0000000..0dbef6c
--- /dev/null
+++ b/talk/session/phone/libudevsymboltable.h
@@ -0,0 +1,58 @@
+/*
+ * libjingle
+ * Copyright 2004--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.
+ */
+
+#ifndef TALK_SESSION_PHONE_LIBUDEVSYMBOLTABLE_H_
+#define TALK_SESSION_PHONE_LIBUDEVSYMBOLTABLE_H_
+
+#include "talk/base/latebindingsymboltable.h"
+
+namespace cricket {
+
+// The libudev symbols we need, as an X-Macro list.
+// This list must contain precisely every libudev function that is used in
+// devicemanager.cc.
+#define LIBUDEV_SYMBOLS_LIST \
+  X(udev_device_unref) \
+  X(udev_monitor_enable_receiving) \
+  X(udev_monitor_filter_add_match_subsystem_devtype) \
+  X(udev_monitor_get_fd) \
+  X(udev_monitor_new_from_netlink) \
+  X(udev_monitor_receive_device) \
+  X(udev_monitor_unref) \
+  X(udev_new) \
+  X(udev_unref)
+
+LATE_BINDING_SYMBOL_TABLE_DECLARE_BEGIN(LibUDevSymbolTable)
+#define X(sym) \
+    LATE_BINDING_SYMBOL_TABLE_DECLARE_ENTRY(LibUDevSymbolTable, sym)
+LIBUDEV_SYMBOLS_LIST
+#undef X
+LATE_BINDING_SYMBOL_TABLE_DECLARE_END(LibUDevSymbolTable)
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_LIBUDEVSYMBOLTABLE_H_
diff --git a/talk/session/phone/mediachannel.h b/talk/session/phone/mediachannel.h
new file mode 100644
index 0000000..bb17db2
--- /dev/null
+++ b/talk/session/phone/mediachannel.h
@@ -0,0 +1,466 @@
+/*
+ * libjingle
+ * Copyright 2004--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.
+ */
+
+#ifndef TALK_SESSION_PHONE_MEDIACHANNEL_H_
+#define TALK_SESSION_PHONE_MEDIACHANNEL_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/socket.h"
+#include "talk/session/phone/codec.h"
+// TODO: re-evaluate this include
+#include "talk/session/phone/audiomonitor.h"
+
+namespace talk_base {
+class Buffer;
+}
+
+namespace flute {
+class MagicCamVideoRenderer;
+}
+
+namespace cricket {
+
+const size_t kMinRtpPacketLen = 12;
+const size_t kMinRtcpPacketLen = 4;
+const size_t kMaxRtpPacketLen = 2048;
+
+enum VoiceMediaChannelOptions {
+  OPT_CONFERENCE = 0x10000,   // tune the audio stream for conference mode
+  OPT_ENERGYLEVEL = 0x20000,  // include the energy level in RTP packets, as
+                              // defined in https://datatracker.ietf.org/drafts/
+                              // draft-lennox-avt-rtp-audio-level-exthdr/
+
+};
+
+enum VideoMediaChannelOptions {
+  OPT_INTERPOLATE = 0x10000   // Increase the output framerate by 2x by
+                              // interpolating frames
+};
+
+class MediaChannel : public sigslot::has_slots<> {
+ public:
+  class NetworkInterface {
+   public:
+    enum SocketType { ST_RTP, ST_RTCP };
+    virtual bool SendPacket(talk_base::Buffer* packet) = 0;
+    virtual bool SendRtcp(talk_base::Buffer* packet) = 0;
+    virtual int SetOption(SocketType type, talk_base::Socket::Option opt,
+                          int option) = 0;
+    virtual ~NetworkInterface() {}
+  };
+
+  MediaChannel() : network_interface_(NULL) {}
+  virtual ~MediaChannel() {}
+
+  // Gets/sets the abstract inteface class for sending RTP/RTCP data.
+  NetworkInterface *network_interface() { return network_interface_; }
+  virtual void SetInterface(NetworkInterface *iface) {
+    network_interface_ = iface;
+  }
+
+  // Called when a RTP packet is received.
+  virtual void OnPacketReceived(talk_base::Buffer* packet) = 0;
+  // Called when a RTCP packet is received.
+  virtual void OnRtcpReceived(talk_base::Buffer* packet) = 0;
+  // Sets the SSRC to be used for outgoing data.
+  virtual void SetSendSsrc(uint32 id) = 0;
+  // Set the CNAME of RTCP
+  virtual bool SetRtcpCName(const std::string& cname) = 0;
+  // Mutes the channel.
+  virtual bool Mute(bool on) = 0;
+
+  virtual bool SetRtpExtensionHeaders(bool enable_all) { return true; }
+  virtual bool SetSendBandwidth(bool autobw, int bps) = 0;
+  virtual bool SetOptions(int options) = 0;
+
+ protected:
+  NetworkInterface *network_interface_;
+};
+
+enum SendFlags {
+  SEND_NOTHING,
+  SEND_RINGBACKTONE,
+  SEND_MICROPHONE
+};
+
+struct VoiceSenderInfo {
+  uint32 ssrc;
+  int bytes_sent;
+  int packets_sent;
+  int packets_lost;
+  float fraction_lost;
+  int ext_seqnum;
+  int rtt_ms;
+  int jitter_ms;
+  int audio_level;
+};
+
+struct VoiceReceiverInfo {
+  uint32 ssrc;
+  int bytes_rcvd;
+  int packets_rcvd;
+  int packets_lost;
+  float fraction_lost;
+  int ext_seqnum;
+  int jitter_ms;
+  int audio_level;
+};
+
+struct VideoSenderInfo {
+  uint32 ssrc;
+  int bytes_sent;
+  int packets_sent;
+  int packets_cached;
+  int packets_lost;
+  float fraction_lost;
+  int firs_rcvd;
+  int nacks_rcvd;
+  int rtt_ms;
+  int frame_width;
+  int frame_height;
+  int framerate_input;
+  int framerate_sent;
+};
+
+struct VideoReceiverInfo {
+  uint32 ssrc;
+  int bytes_rcvd;
+  // vector<int> layer_bytes_rcvd;
+  int packets_rcvd;
+  int packets_lost;
+  int packets_concealed;
+  float fraction_lost;
+  int firs_sent;
+  int nacks_sent;
+  int frame_width;
+  int frame_height;
+  int framerate_rcvd;
+  int framerate_decoded;
+  int framerate_output;
+};
+
+struct VoiceMediaInfo {
+  void Clear() {
+    senders.clear();
+    receivers.clear();
+  }
+  std::vector<VoiceSenderInfo> senders;
+  std::vector<VoiceReceiverInfo> receivers;
+};
+
+struct VideoMediaInfo {
+  void Clear() {
+    senders.clear();
+    receivers.clear();
+  }
+  std::vector<VideoSenderInfo> senders;
+  std::vector<VideoReceiverInfo> receivers;
+};
+
+class VoiceMediaChannel : public MediaChannel {
+ public:
+  enum Error {
+    ERROR_NONE = 0,                       // No error.
+    ERROR_OTHER,                          // Other errors.
+    ERROR_REC_DEVICE_OPEN_FAILED = 100,   // Could not open mic.
+    ERROR_REC_DEVICE_MUTED,               // Mic was muted by OS.
+    ERROR_REC_DEVICE_SILENT,              // No background noise picked up.
+    ERROR_REC_DEVICE_SATURATION,          // Mic input is clipping.
+    ERROR_REC_DEVICE_REMOVED,             // Mic was removed while active.
+    ERROR_REC_RUNTIME_ERROR,              // Processing is encountering errors.
+    ERROR_REC_SRTP_ERROR,                 // Generic SRTP failure.
+    ERROR_PLAY_DEVICE_OPEN_FAILED = 200,  // Could not open playout.
+    ERROR_PLAY_DEVICE_MUTED,              // Playout muted by OS.
+    ERROR_PLAY_DEVICE_REMOVED,            // Playout removed while active.
+    ERROR_PLAY_RUNTIME_ERROR,             // Errors in voice processing.
+    ERROR_PLAY_SRTP_ERROR,                // Generic SRTP failure.
+    ERROR_PLAY_SRTP_AUTH_FAILED,          // Failed to authenticate packets.
+  };
+
+  VoiceMediaChannel() {}
+  virtual ~VoiceMediaChannel() {}
+  // Sets the codecs/payload types to be used for incoming media.
+  virtual bool SetRecvCodecs(const std::vector<AudioCodec>& codecs) = 0;
+  // Sets the codecs/payload types to be used for outgoing media.
+  virtual bool SetSendCodecs(const std::vector<AudioCodec>& codecs) = 0;
+  // Starts or stops playout of received audio.
+  virtual bool SetPlayout(bool playout) = 0;
+  // Starts or stops sending (and potentially capture) of local audio.
+  virtual bool SetSend(SendFlags flag) = 0;
+  // Adds a new receive-only stream with the specified SSRC.
+  virtual bool AddStream(uint32 ssrc) = 0;
+  // Removes a stream added with AddStream.
+  virtual bool RemoveStream(uint32 ssrc) = 0;
+  // Gets current energy levels for all incoming streams.
+  virtual bool GetActiveStreams(AudioInfo::StreamList* actives) = 0;
+  // Get the current energy level for the outgoing stream.
+  virtual int GetOutputLevel() = 0;
+  // Specifies a ringback tone to be played during call setup.
+  virtual void SetRingbackTone(const char *buf, int len) = 0;
+  // Plays or stops the aforementioned ringback tone
+  virtual bool PlayRingbackTone(bool play, bool loop) = 0;
+  // Sends a out-of-band DTMF signal using the specified event.
+  virtual bool PressDTMF(int event, bool playout) = 0;
+  // Gets quality stats for the channel.
+  virtual bool GetStats(VoiceMediaInfo* info) = 0;
+  // Gets last reported error for this media channel.
+  virtual void GetLastMediaError(uint32* ssrc,
+                                 VoiceMediaChannel::Error* error) {
+    ASSERT(error != NULL);
+    *error = ERROR_NONE;
+  }
+
+  // Signal errors from MediaChannel.  Arguments are:
+  //     ssrc(uint32), and error(VoiceMediaChannel::Error).
+  sigslot::signal2<uint32, VoiceMediaChannel::Error> SignalMediaError;
+};
+
+// Represents a YUV420 (a.k.a. I420) video frame.
+class VideoFrame {
+  friend class flute::MagicCamVideoRenderer;
+
+ public:
+  VideoFrame() : rendered_(false) {}
+
+  virtual ~VideoFrame() {}
+
+  virtual size_t GetWidth() const = 0;
+  virtual size_t GetHeight() const = 0;
+  virtual const uint8 *GetYPlane() const = 0;
+  virtual const uint8 *GetUPlane() const = 0;
+  virtual const uint8 *GetVPlane() const = 0;
+  virtual uint8 *GetYPlane() = 0;
+  virtual uint8 *GetUPlane() = 0;
+  virtual uint8 *GetVPlane() = 0;
+  virtual int32 GetYPitch() const = 0;
+  virtual int32 GetUPitch() const = 0;
+  virtual int32 GetVPitch() const = 0;
+
+  // For retrieving the aspect ratio of each pixel. Usually this is 1x1, but
+  // the aspect_ratio_idc parameter of H.264 can specify non-square pixels.
+  virtual size_t GetPixelWidth() const = 0;
+  virtual size_t GetPixelHeight() const = 0;
+
+  // TODO: Add a fourcc format here and probably combine VideoFrame
+  // with CapturedFrame.
+  virtual int64 GetElapsedTime() const = 0;
+  virtual int64 GetTimeStamp() const = 0;
+  virtual void SetElapsedTime(int64 elapsed_time) = 0;
+  virtual void SetTimeStamp(int64 time_stamp) = 0;
+
+  // Make a copy of the frame. The frame buffer itself may not be copied,
+  // in which case both the current and new VideoFrame will share a single
+  // reference-counted frame buffer.
+  virtual VideoFrame *Copy() const = 0;
+
+  // Writes the frame into the given frame buffer, provided that it is of
+  // sufficient size. Returns the frame's actual size, regardless of whether
+  // it was written or not (like snprintf). If there is insufficient space,
+  // nothing is written.
+  virtual size_t CopyToBuffer(uint8 *buffer, size_t size) const = 0;
+
+  // Converts the I420 data to RGB of a certain type such as ARGB and ABGR.
+  // Returns the frame's actual size, regardless of whether it was written or
+  // not (like snprintf). Parameters size and pitch_rgb are in units of bytes.
+  // If there is insufficient space, nothing is written.
+  virtual size_t ConvertToRgbBuffer(uint32 to_fourcc, uint8 *buffer,
+                                    size_t size, size_t pitch_rgb) const = 0;
+
+  // Writes the frame into the given planes, stretched to the given width and
+  // height. The parameter "interpolate" controls whether to interpolate or just
+  // take the nearest-point. The parameter "crop" controls whether to crop this
+  // frame to the aspect ratio of the given dimensions before stretching.
+  virtual void StretchToPlanes(uint8 *y, uint8 *u, uint8 *v,
+                               int32 pitchY, int32 pitchU, int32 pitchV,
+                               size_t width, size_t height,
+                               bool interpolate, bool crop) const = 0;
+
+  // Writes the frame into the given frame buffer, stretched to the given width
+  // and height, provided that it is of sufficient size. Returns the frame's
+  // actual size, regardless of whether it was written or not (like snprintf).
+  // If there is insufficient space, nothing is written. The parameter
+  // "interpolate" controls whether to interpolate or just take the
+  // nearest-point. The parameter "crop" controls whether to crop this frame to
+  // the aspect ratio of the given dimensions before stretching.
+  virtual size_t StretchToBuffer(size_t w, size_t h, uint8 *buffer, size_t size,
+                                 bool interpolate, bool crop) const = 0;
+
+  // Writes the frame into the target VideoFrame, stretched to the size of that
+  // frame. The parameter "interpolate" controls whether to interpolate or just
+  // take the nearest-point. The parameter "crop" controls whether to crop this
+  // frame to the aspect ratio of the target frame before stretching.
+  virtual void StretchToFrame(VideoFrame *target, bool interpolate,
+                              bool crop) const = 0;
+
+  // Stretches the frame to the given size, creating a new VideoFrame object to
+  // hold it. The parameter "interpolate" controls whether to interpolate or
+  // just take the nearest-point. The parameter "crop" controls whether to crop
+  // this frame to the aspect ratio of the given dimensions before stretching.
+  virtual VideoFrame *Stretch(size_t w, size_t h, bool interpolate,
+                              bool crop) const = 0;
+
+  // Size of an I420 image of given dimensions when stored as a frame buffer.
+  static size_t SizeOf(size_t w, size_t h) {
+    return w * h + ((w + 1) / 2) * ((h + 1) / 2) * 2;
+  }
+
+ protected:
+  // The frame needs to be rendered to magiccam only once.
+  // TODO: Remove this flag once magiccam rendering is fully replaced
+  // by client3d rendering.
+  mutable bool rendered_;
+};
+
+// Simple subclass for use in mocks.
+class NullVideoFrame : public VideoFrame {
+ public:
+  virtual size_t GetWidth() const { return 0; }
+  virtual size_t GetHeight() const { return 0; }
+  virtual const uint8 *GetYPlane() const { return NULL; }
+  virtual const uint8 *GetUPlane() const { return NULL; }
+  virtual const uint8 *GetVPlane() const { return NULL; }
+  virtual uint8 *GetYPlane() { return NULL; }
+  virtual uint8 *GetUPlane() { return NULL; }
+  virtual uint8 *GetVPlane() { return NULL; }
+  virtual int32 GetYPitch() const { return 0; }
+  virtual int32 GetUPitch() const { return 0; }
+  virtual int32 GetVPitch() const { return 0; }
+
+  virtual size_t GetPixelWidth() const { return 1; }
+  virtual size_t GetPixelHeight() const { return 1; }
+  virtual int64 GetElapsedTime() const { return 0; }
+  virtual int64 GetTimeStamp() const { return 0; }
+  virtual void SetElapsedTime(int64 elapsed_time) {}
+  virtual void SetTimeStamp(int64 time_stamp) {}
+
+  virtual VideoFrame *Copy() const {
+    return NULL;
+  }
+
+  virtual size_t CopyToBuffer(uint8 *buffer, size_t size) const {
+    return 0;
+  }
+
+  virtual size_t ConvertToRgbBuffer(uint32 to_fourcc, uint8 *buffer,
+                                    size_t size, size_t pitch_rgb) const {
+    return 0;
+  }
+
+  virtual void StretchToPlanes(uint8 *y, uint8 *u, uint8 *v,
+                               int32 pitchY, int32 pitchU, int32 pitchV,
+                               size_t width, size_t height,
+                               bool interpolate, bool crop) const {
+  }
+
+  virtual size_t StretchToBuffer(size_t w, size_t h, uint8 *buffer, size_t size,
+                                 bool interpolate, bool crop) const {
+    return 0;
+  }
+
+  virtual void StretchToFrame(VideoFrame *target, bool interpolate,
+                              bool crop) const {
+  }
+
+  virtual VideoFrame *Stretch(size_t w, size_t h, bool interpolate,
+                              bool crop) const {
+    return NULL;
+  }
+};
+
+// Abstract interface for rendering VideoFrames.
+class VideoRenderer {
+ public:
+  virtual ~VideoRenderer() {}
+  // Called when the video has changed size.
+  virtual bool SetSize(int width, int height, int reserved) = 0;
+  // Called when a new frame is available for display.
+  virtual bool RenderFrame(const VideoFrame *frame) = 0;
+};
+
+// Simple implementation for use in tests.
+class NullVideoRenderer : public VideoRenderer {
+  virtual bool SetSize(int width, int height, int reserved) {
+    return true;
+  }
+  // Called when a new frame is available for display.
+  virtual bool RenderFrame(const VideoFrame *frame) {
+    return true;
+  }
+};
+
+class VideoMediaChannel : public MediaChannel {
+ public:
+  enum Error {
+    ERROR_NONE = 0,                       // No error.
+    ERROR_OTHER,                          // Other errors.
+    ERROR_REC_DEVICE_OPEN_FAILED = 100,   // Could not open camera.
+    ERROR_REC_DEVICE_NO_DEVICE,           // No camera.
+    ERROR_REC_DEVICE_IN_USE,              // Device is in already use.
+    ERROR_REC_DEVICE_REMOVED,             // Device is removed.
+    ERROR_REC_SRTP_ERROR,                 // Generic sender SRTP failure.
+    ERROR_PLAY_SRTP_ERROR = 200,          // Generic receiver SRTP failure.
+    ERROR_PLAY_SRTP_AUTH_FAILED,          // Failed to authenticate packets.
+  };
+
+  VideoMediaChannel() { renderer_ = NULL; }
+  virtual ~VideoMediaChannel() {}
+  // Sets the codecs/payload types to be used for incoming media.
+  virtual bool SetRecvCodecs(const std::vector<VideoCodec> &codecs) = 0;
+  // Sets the codecs/payload types to be used for outgoing media.
+  virtual bool SetSendCodecs(const std::vector<VideoCodec> &codecs) = 0;
+  // Starts or stops playout of received video.
+  virtual bool SetRender(bool render) = 0;
+  // Starts or stops transmission (and potentially capture) of local video.
+  virtual bool SetSend(bool send) = 0;
+  // Adds a new receive-only stream with the specified SSRC.
+  virtual bool AddStream(uint32 ssrc, uint32 voice_ssrc) = 0;
+  // Removes a stream added with AddStream.
+  virtual bool RemoveStream(uint32 ssrc) = 0;
+  // Sets the renderer object to be used for the specified stream.
+  // If SSRC is 0, the renderer is used for the 'default' stream.
+  virtual bool SetRenderer(uint32 ssrc, VideoRenderer* renderer) = 0;
+  // Gets quality stats for the channel.
+  virtual bool GetStats(VideoMediaInfo* info) = 0;
+
+  // Send an intra frame to the receivers.
+  virtual bool SendIntraFrame() = 0;
+  // Reuqest each of the remote senders to send an intra frame.
+  virtual bool RequestIntraFrame() = 0;
+
+  sigslot::signal2<uint32, Error> SignalMediaError;
+
+ protected:
+  VideoRenderer *renderer_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_MEDIACHANNEL_H_
diff --git a/talk/session/phone/mediaengine.cc b/talk/session/phone/mediaengine.cc
new file mode 100644
index 0000000..42b0954
--- /dev/null
+++ b/talk/session/phone/mediaengine.cc
@@ -0,0 +1,40 @@
+//
+// 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/session/phone/mediaengine.h"
+
+
+namespace cricket {
+
+// TODO: according to thaloun, HAVE_GIPSVIDEO will always
+// be false, so we can get rid of it.
+
+MediaEngine* MediaEngine::Create() {
+  return new NullMediaEngine();
+}
+
+};  // namespace cricket
diff --git a/talk/session/phone/mediaengine.h b/talk/session/phone/mediaengine.h
new file mode 100644
index 0000000..234a668
--- /dev/null
+++ b/talk/session/phone/mediaengine.h
@@ -0,0 +1,337 @@
+/*
+ * 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.
+ */
+
+#ifndef TALK_SESSION_PHONE_MEDIAENGINE_H_
+#define TALK_SESSION_PHONE_MEDIAENGINE_H_
+
+#ifdef OSX
+#include <CoreAudio/CoreAudio.h>
+#endif
+
+#include <string>
+#include <vector>
+
+#include "talk/base/sigslotrepeater.h"
+#include "talk/session/phone/codec.h"
+#include "talk/session/phone/devicemanager.h"
+#include "talk/session/phone/mediachannel.h"
+#include "talk/session/phone/videocommon.h"
+
+namespace cricket {
+
+// A class for playing out soundclips.
+class SoundclipMedia {
+ public:
+  enum SoundclipFlags {
+    SF_LOOP = 1,
+  };
+
+  virtual ~SoundclipMedia() {}
+
+  // Plays a sound out to the speakers with the given audio stream. The stream
+  // must be 16-bit little-endian 16 kHz PCM. If a stream is already playing
+  // on this SoundclipMedia, it is stopped. If clip is NULL, nothing is played.
+  // Returns whether it was successful.
+  virtual bool PlaySound(const char *clip, int len, int flags) = 0;
+};
+
+// MediaEngine is an abstraction of a media engine which can be subclassed
+// to support different media componentry backends. It supports voice and
+// video operations in the same class to facilitate proper synchronization
+// between both media types.
+class MediaEngine {
+ public:
+  // TODO: Move this to a global location (also used in DeviceManager)
+  // Capabilities of the media engine.
+  enum Capabilities {
+    AUDIO_RECV = 1 << 0,
+    AUDIO_SEND = 1 << 1,
+    VIDEO_RECV = 1 << 2,
+    VIDEO_SEND = 1 << 3,
+  };
+
+  // Bitmask flags for options that may be supported by the media engine
+  // implementation
+  enum AudioOptions {
+    ECHO_CANCELLATION = 1 << 0,
+    AUTO_GAIN_CONTROL = 1 << 1,
+    DEFAULT_AUDIO_OPTIONS = ECHO_CANCELLATION | AUTO_GAIN_CONTROL
+  };
+  enum VideoOptions {
+  };
+
+  virtual ~MediaEngine() {}
+  static MediaEngine* Create();
+
+  // Initialization
+  // Starts the engine.
+  virtual bool Init() = 0;
+  // Shuts down the engine.
+  virtual void Terminate() = 0;
+  // Returns what the engine is capable of, as a set of Capabilities, above.
+  virtual int GetCapabilities() = 0;
+
+  // MediaChannel creation
+  // Creates a voice media channel. Returns NULL on failure.
+  virtual VoiceMediaChannel *CreateChannel() = 0;
+  // Creates a video media channel, paired with the specified voice channel.
+  // Returns NULL on failure.
+  virtual VideoMediaChannel *CreateVideoChannel(
+      VoiceMediaChannel* voice_media_channel) = 0;
+
+  // Creates a soundclip object for playing sounds on. Returns NULL on failure.
+  virtual SoundclipMedia *CreateSoundclip() = 0;
+
+  // Configuration
+  // Sets global audio options. "options" are from AudioOptions, above.
+  virtual bool SetAudioOptions(int options) = 0;
+  // Sets global video options. "options" are from VideoOptions, above.
+  virtual bool SetVideoOptions(int options) = 0;
+  // Sets the default (maximum) codec/resolution and encoder option to capture
+  // and encode video.
+  virtual bool SetDefaultVideoEncoderConfig(const VideoEncoderConfig& config)
+      = 0;
+
+  // Device selection
+  // TODO: Add method for selecting the soundclip device.
+  virtual bool SetSoundDevices(const Device* in_device,
+                               const Device* out_device) = 0;
+  virtual bool SetVideoCaptureDevice(const Device* cam_device) = 0;
+
+  // Device configuration
+  // Sets the current speaker volume, as a value between 0 and 255.
+  virtual bool SetOutputVolume(int level) = 0;
+
+  // Local monitoring
+  // Gets the current microphone level, as a value between 0 and 10.
+  virtual int GetInputLevel() = 0;
+  // Starts or stops the local microphone. Useful if local mic info is needed
+  // prior to a call being connected; the mic will be started automatically
+  // when a VoiceMediaChannel starts sending.
+  virtual bool SetLocalMonitor(bool enable) = 0;
+  // Installs a callback for raw frames from the local camera.
+  virtual bool SetLocalRenderer(VideoRenderer* renderer) = 0;
+  // Starts/stops local camera.
+  virtual CaptureResult SetVideoCapture(bool capture) = 0;
+
+  virtual const std::vector<AudioCodec>& audio_codecs() = 0;
+  virtual const std::vector<VideoCodec>& video_codecs() = 0;
+  virtual bool FindAudioCodec(const AudioCodec &codec) = 0;
+  virtual bool FindVideoCodec(const VideoCodec &codec) = 0;
+
+  // Logging control
+  virtual void SetVoiceLogging(int min_sev, const char* filter) = 0;
+  virtual void SetVideoLogging(int min_sev, const char* filter) = 0;
+
+  sigslot::repeater1<CaptureResult> SignalVideoCaptureResult;
+};
+
+// CompositeMediaEngine constructs a MediaEngine from separate
+// voice and video engine classes.
+template<class VOICE, class VIDEO>
+class CompositeMediaEngine : public MediaEngine {
+ public:
+  CompositeMediaEngine() {}
+  virtual bool Init() {
+    if (!voice_.Init())
+      return false;
+    if (!video_.Init()) {
+      voice_.Terminate();
+      return false;
+    }
+    SignalVideoCaptureResult.repeat(video_.SignalCaptureResult);
+    return true;
+  }
+  virtual void Terminate() {
+    video_.Terminate();
+    voice_.Terminate();
+  }
+
+  virtual int GetCapabilities() {
+    return (voice_.GetCapabilities() | video_.GetCapabilities());
+  }
+  virtual VoiceMediaChannel *CreateChannel() {
+    return voice_.CreateChannel();
+  }
+  virtual VideoMediaChannel *CreateVideoChannel(VoiceMediaChannel* channel) {
+    return video_.CreateChannel(channel);
+  }
+  virtual SoundclipMedia *CreateSoundclip() {
+    return voice_.CreateSoundclip();
+  }
+
+  virtual bool SetAudioOptions(int o) {
+    return voice_.SetOptions(o);
+  }
+  virtual bool SetVideoOptions(int o) {
+    return video_.SetOptions(o);
+  }
+  virtual bool SetDefaultVideoEncoderConfig(const VideoEncoderConfig& config) {
+    return video_.SetDefaultEncoderConfig(config);
+  }
+
+  virtual bool SetSoundDevices(const Device* in_device,
+                               const Device* out_device) {
+    return voice_.SetDevices(in_device, out_device);
+  }
+  virtual bool SetVideoCaptureDevice(const Device* cam_device) {
+    return video_.SetCaptureDevice(cam_device);
+  }
+
+  virtual bool SetOutputVolume(int level) {
+    return voice_.SetOutputVolume(level);
+  }
+
+  virtual int GetInputLevel() {
+    return voice_.GetInputLevel();
+  }
+  virtual bool SetLocalMonitor(bool enable) {
+    return voice_.SetLocalMonitor(enable);
+  }
+  virtual bool SetLocalRenderer(VideoRenderer* renderer) {
+    return video_.SetLocalRenderer(renderer);
+  }
+  virtual CaptureResult SetVideoCapture(bool capture) {
+    return video_.SetCapture(capture);
+  }
+
+  virtual const std::vector<AudioCodec>& audio_codecs() {
+    return voice_.codecs();
+  }
+  virtual const std::vector<VideoCodec>& video_codecs() {
+    return video_.codecs();
+  }
+
+  virtual bool FindAudioCodec(const AudioCodec &codec) {
+    return voice_.FindCodec(codec);
+  }
+  virtual bool FindVideoCodec(const VideoCodec &codec) {
+    return video_.FindCodec(codec);
+  }
+
+  virtual void SetVoiceLogging(int min_sev, const char* filter) {
+    return voice_.SetLogging(min_sev, filter);
+  }
+  virtual void SetVideoLogging(int min_sev, const char* filter) {
+    return video_.SetLogging(min_sev, filter);
+  }
+
+ private:
+  VOICE voice_;
+  VIDEO video_;
+};
+
+class NullVoiceMediaChannel : public VoiceMediaChannel {
+ public:
+  explicit NullVoiceMediaChannel() {}
+  ~NullVoiceMediaChannel() {}
+  // MediaChannel implementations
+  virtual void OnPacketReceived(talk_base::Buffer* packet) {}
+  virtual void OnRtcpReceived(talk_base::Buffer* packet) {}
+  virtual void SetSendSsrc(uint32 id) {}
+  virtual bool SetRtcpCName(const std::string& cname) { return true; }
+  virtual bool Mute(bool on) { return true; }
+  virtual bool SetSendBandwidth(bool autobw, int bps) { return true; }
+  virtual bool SetOptions(int options) { return true; }
+  // VoiceMediaChannel implementations
+  virtual bool SetRecvCodecs(const std::vector<AudioCodec> &codecs) {
+    return true;
+  }
+  virtual bool SetSendCodecs(const std::vector<AudioCodec> &codecs) {
+    return true;
+  }
+  virtual bool SetPlayout(bool playout) { return true; }
+  virtual bool SetSend(SendFlags flag) { return true; }
+  virtual bool AddStream(uint32 ssrc) { return true; }
+  virtual bool RemoveStream(uint32 ssrc) { return true; }
+  virtual bool GetActiveStreams(AudioInfo::StreamList* streams) { return true; }
+  virtual int GetOutputLevel() { return 0; }
+  virtual void SetRingbackTone(const char *buf, int len) {}
+  virtual bool PlayRingbackTone(bool play, bool loop) { return true; }
+  virtual bool PressDTMF(int event, bool playout) { return true; }
+  virtual bool GetStats(VoiceMediaInfo* info) { return false; }
+};
+
+// NullVoiceEngine can be used with CompositeMediaEngine in the case where only
+// a video engine is desired.
+class NullVoiceEngine {
+ public:
+  bool Init() { return true; }
+  void Terminate() {}
+  int GetCapabilities() { return 0; }
+  VoiceMediaChannel* CreateChannel() {
+    // TODO: See if we can make things work without requiring
+    // allocation of a channel.
+    return new NullVoiceMediaChannel();
+  }
+  SoundclipMedia* CreateSoundclip() {
+    return NULL;
+  }
+  bool SetOptions(int opts) { return true; }
+  bool SetDevices(const Device* in_device, const Device* out_device) {
+    return true;
+  }
+  bool SetOutputVolume(int level) { return true; }
+  int GetInputLevel() { return 0; }
+  bool SetLocalMonitor(bool enable) { return true; }
+  const std::vector<AudioCodec>& codecs() { return codecs_; }
+  bool FindCodec(const AudioCodec&) { return false; }
+  void SetLogging(int min_sev, const char* filter) {}
+ private:
+  std::vector<AudioCodec> codecs_;
+};
+
+// NullVideoEngine can be used with CompositeMediaEngine in the case where only
+// a voice engine is desired.
+class NullVideoEngine {
+ public:
+  bool Init() { return true; }
+  void Terminate() {}
+  int GetCapabilities() { return 0; }
+  VideoMediaChannel* CreateChannel(VoiceMediaChannel* voice_media_channel) {
+    return NULL;
+  }
+  bool SetOptions(int opts) { return true; }
+  bool SetDefaultEncoderConfig(const VideoEncoderConfig& config) {
+    return true;
+  }
+  bool SetCaptureDevice(const Device* cam_device) { return true; }
+  bool SetLocalRenderer(VideoRenderer* renderer) { return true; }
+  CaptureResult SetCapture(bool capture) { return CR_SUCCESS;  }
+  const std::vector<VideoCodec>& codecs() { return codecs_; }
+  bool FindCodec(const VideoCodec&) { return false; }
+  void SetLogging(int min_sev, const char* filter) {}
+  sigslot::signal1<CaptureResult> SignalCaptureResult;
+ private:
+  std::vector<VideoCodec> codecs_;
+};
+
+typedef CompositeMediaEngine<NullVoiceEngine, NullVideoEngine> NullMediaEngine;
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_MEDIAENGINE_H_
diff --git a/talk/session/phone/mediamonitor.cc b/talk/session/phone/mediamonitor.cc
new file mode 100644
index 0000000..909b536
--- /dev/null
+++ b/talk/session/phone/mediamonitor.cc
@@ -0,0 +1,109 @@
+/*
+ * libjingle
+ * Copyright 2005--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/base/common.h"
+#include "talk/session/phone/mediamonitor.h"
+#include "talk/session/phone/channelmanager.h"
+#include "talk/session/phone/mediaengine.h"
+
+namespace cricket {
+
+enum {
+  MSG_MONITOR_POLL = 1,
+  MSG_MONITOR_START = 2,
+  MSG_MONITOR_STOP = 3,
+  MSG_MONITOR_SIGNAL = 4
+};
+
+MediaMonitor::MediaMonitor(talk_base::Thread* worker_thread,
+                           talk_base::Thread* monitor_thread)
+    : worker_thread_(worker_thread),
+      monitor_thread_(monitor_thread), monitoring_(false), rate_(0) {
+}
+
+MediaMonitor::~MediaMonitor() {
+  monitoring_ = false;
+  monitor_thread_->Clear(this);
+  worker_thread_->Clear(this);
+}
+
+void MediaMonitor::Start(uint32 milliseconds) {
+  rate_ = milliseconds;
+  if (rate_ < 100)
+    rate_ = 100;
+  worker_thread_->Post(this, MSG_MONITOR_START);
+}
+
+void MediaMonitor::Stop() {
+  worker_thread_->Post(this, MSG_MONITOR_STOP);
+  rate_ = 0;
+}
+
+void MediaMonitor::OnMessage(talk_base::Message* message) {
+  talk_base::CritScope cs(&crit_);
+
+  switch (message->message_id) {
+  case MSG_MONITOR_START:
+    ASSERT(talk_base::Thread::Current() == worker_thread_);
+    if (!monitoring_) {
+      monitoring_ = true;
+      PollMediaChannel();
+    }
+    break;
+
+  case MSG_MONITOR_STOP:
+    ASSERT(talk_base::Thread::Current() == worker_thread_);
+    if (monitoring_) {
+      monitoring_ = false;
+      worker_thread_->Clear(this);
+    }
+    break;
+
+  case MSG_MONITOR_POLL:
+    ASSERT(talk_base::Thread::Current() == worker_thread_);
+    PollMediaChannel();
+    break;
+
+  case MSG_MONITOR_SIGNAL:
+    ASSERT(talk_base::Thread::Current() == monitor_thread_);
+    Update();
+    break;
+  }
+}
+
+void MediaMonitor::PollMediaChannel() {
+  talk_base::CritScope cs(&crit_);
+  ASSERT(talk_base::Thread::Current() == worker_thread_);
+
+  GetStats();
+
+  // Signal the monitoring thread, start another poll timer
+  monitor_thread_->Post(this, MSG_MONITOR_SIGNAL);
+  worker_thread_->PostDelayed(rate_, this, MSG_MONITOR_POLL);
+}
+
+}
diff --git a/talk/session/phone/mediamonitor.h b/talk/session/phone/mediamonitor.h
new file mode 100644
index 0000000..6b964aa
--- /dev/null
+++ b/talk/session/phone/mediamonitor.h
@@ -0,0 +1,98 @@
+/*
+ * libjingle
+ * Copyright 2005--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.
+ */
+
+// Class to collect statistics from a media channel
+
+#ifndef TALK_SESSION_PHONE_MEDIAMONITOR_H_
+#define TALK_SESSION_PHONE_MEDIAMONITOR_H_
+
+#include "talk/base/thread.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/criticalsection.h"
+#include "talk/session/phone/mediachannel.h"
+
+namespace cricket {
+
+// The base MediaMonitor class, independent of voice and video.
+class MediaMonitor : public talk_base::MessageHandler,
+    public sigslot::has_slots<> {
+ public:
+  MediaMonitor(talk_base::Thread* worker_thread,
+               talk_base::Thread* monitor_thread);
+  ~MediaMonitor();
+
+  void Start(uint32 milliseconds);
+  void Stop();
+
+ protected:
+  void OnMessage(talk_base::Message *message);
+  void PollMediaChannel();
+  virtual void GetStats() = 0;
+  virtual void Update() = 0;
+
+  talk_base::CriticalSection crit_;
+  talk_base::Thread* worker_thread_;
+  talk_base::Thread* monitor_thread_;
+  bool monitoring_;
+  uint32 rate_;
+};
+
+// Templatized MediaMonitor that can deal with different kinds of media.
+template<class MC, class MI>
+class MediaMonitorT : public MediaMonitor {
+ public:
+  MediaMonitorT(MC* media_channel, talk_base::Thread* worker_thread,
+                talk_base::Thread* monitor_thread)
+      : MediaMonitor(worker_thread, monitor_thread),
+        media_channel_(media_channel) {}
+  sigslot::signal2<MC*, const MI&> SignalUpdate;
+
+ protected:
+  // These routines assume the crit_ lock is held by the calling thread.
+  virtual void GetStats() {
+    media_info_.Clear();
+    media_channel_->GetStats(&media_info_);
+  }
+  virtual void Update() {
+    MI stats(media_info_);
+    crit_.Leave();
+    SignalUpdate(media_channel_, stats);
+    crit_.Enter();
+  }
+
+ private:
+  MC* media_channel_;
+  MI media_info_;
+};
+
+typedef MediaMonitorT<VoiceMediaChannel, VoiceMediaInfo> VoiceMediaMonitor;
+typedef MediaMonitorT<VideoMediaChannel, VideoMediaInfo> VideoMediaMonitor;
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_MEDIAMONITOR_H_
+
diff --git a/talk/session/phone/mediasessionclient.cc b/talk/session/phone/mediasessionclient.cc
new file mode 100644
index 0000000..c862cd5
--- /dev/null
+++ b/talk/session/phone/mediasessionclient.cc
@@ -0,0 +1,1020 @@
+/*
+ * 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 <string>
+
+#include "talk/session/phone/mediasessionclient.h"
+
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/stringencode.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/parsing.h"
+#include "talk/session/phone/cryptoparams.h"
+#include "talk/session/phone/srtpfilter.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlconstants.h"
+
+using namespace talk_base;
+
+namespace {
+const std::string kInline = "inline:";
+}
+
+namespace cricket {
+
+typedef std::vector<CryptoParams> CryptoParamsVec;
+
+MediaSessionClient::MediaSessionClient(
+    const buzz::Jid& jid, SessionManager *manager)
+    : jid_(jid), session_manager_(manager), focus_call_(NULL),
+      channel_manager_(new ChannelManager(session_manager_->worker_thread())),
+      secure_(SEC_DISABLED) {
+  Construct();
+}
+
+MediaSessionClient::MediaSessionClient(
+    const buzz::Jid& jid, SessionManager *manager,
+    MediaEngine* media_engine, DeviceManager* device_manager)
+    : jid_(jid), session_manager_(manager), focus_call_(NULL),
+      channel_manager_(new ChannelManager(
+          media_engine, device_manager, session_manager_->worker_thread())),
+      secure_(SEC_DISABLED) {
+  Construct();
+}
+
+
+void MediaSessionClient::Construct() {
+  // Register ourselves as the handler of phone 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);
+}
+
+bool CreateCryptoParams(int tag, const std::string& cipher, CryptoParams *out) {
+  std::string key;
+  key.reserve(SRTP_MASTER_KEY_BASE64_LEN);
+
+  if (!CreateRandomString(SRTP_MASTER_KEY_BASE64_LEN, &key)) {
+    return false;
+  }
+  out->tag = tag;
+  out->cipher_suite = cipher;
+  out->key_params = kInline + key;
+  return true;
+}
+
+bool AddCryptoParams(const std::string& cipher_suite, CryptoParamsVec *out) {
+  int size = out->size();
+
+  out->resize(size + 1);
+  return CreateCryptoParams(size, cipher_suite, &out->at(size));
+}
+
+// For audio, HMAC 32 is prefered because of the low overhead.
+bool GetSupportedAudioCryptos(CryptoParamsVec* cryptos) {
+#ifdef HAVE_SRTP
+  return AddCryptoParams(CS_AES_CM_128_HMAC_SHA1_32, cryptos) &&
+      AddCryptoParams(CS_AES_CM_128_HMAC_SHA1_80, cryptos);
+#else
+  return false;
+#endif
+}
+
+bool GetSupportedVideoCryptos(CryptoParamsVec* cryptos) {
+#ifdef HAVE_SRTP
+  return AddCryptoParams(CS_AES_CM_128_HMAC_SHA1_80, cryptos);
+#else
+  return false;
+#endif
+}
+
+SessionDescription* MediaSessionClient::CreateOffer(
+    const CallOptions& options) {
+  SessionDescription* offer = new SessionDescription();
+  AudioContentDescription* audio = new AudioContentDescription();
+
+
+  AudioCodecs audio_codecs;
+  channel_manager_->GetSupportedAudioCodecs(&audio_codecs);
+  for (AudioCodecs::const_iterator codec = audio_codecs.begin();
+       codec != audio_codecs.end(); ++codec) {
+    audio->AddCodec(*codec);
+  }
+  if (options.is_muc) {
+    audio->set_ssrc(0);
+  }
+  audio->SortCodecs();
+
+  if (secure() != SEC_DISABLED) {
+    CryptoParamsVec audio_cryptos;
+    if (GetSupportedAudioCryptos(&audio_cryptos)) {
+      for (CryptoParamsVec::const_iterator crypto = audio_cryptos.begin();
+           crypto != audio_cryptos.end(); ++crypto) {
+        audio->AddCrypto(*crypto);
+      }
+    }
+    if (secure() == SEC_REQUIRED) {
+      if (audio->cryptos().empty()) {
+        return NULL;  // Abort, crypto required but none found.
+      }
+      audio->set_crypto_required(true);
+    }
+  }
+
+  offer->AddContent(CN_AUDIO, NS_JINGLE_RTP, audio);
+
+  // add video codecs, if this is a video call
+  if (options.is_video) {
+    VideoContentDescription* video = new VideoContentDescription();
+    VideoCodecs video_codecs;
+    channel_manager_->GetSupportedVideoCodecs(&video_codecs);
+    for (VideoCodecs::const_iterator codec = video_codecs.begin();
+         codec != video_codecs.end(); ++codec) {
+      video->AddCodec(*codec);
+    }
+    if (options.is_muc) {
+      video->set_ssrc(0);
+    }
+    video->set_bandwidth(options.video_bandwidth);
+    video->SortCodecs();
+
+    if (secure() != SEC_DISABLED) {
+      CryptoParamsVec video_cryptos;
+      if (GetSupportedVideoCryptos(&video_cryptos)) {
+        for (CryptoParamsVec::const_iterator crypto = video_cryptos.begin();
+             crypto != video_cryptos.end(); ++crypto) {
+          video->AddCrypto(*crypto);
+        }
+      }
+      if (secure() == SEC_REQUIRED) {
+        if (video->cryptos().empty()) {
+          return NULL;  // Abort, crypto required but none found.
+        }
+        video->set_crypto_required(true);
+      }
+    }
+
+    offer->AddContent(CN_VIDEO, NS_JINGLE_RTP, video);
+  }
+
+  return offer;
+}
+
+const ContentInfo* GetFirstMediaContent(const SessionDescription* sdesc,
+                                        MediaType media_type) {
+  if (sdesc == NULL)
+    return NULL;
+
+  const ContentInfos& contents = sdesc->contents();
+  for (ContentInfos::const_iterator content = contents.begin();
+       content != contents.end(); content++) {
+    if (content->type == NS_JINGLE_RTP) {
+      const MediaContentDescription* media =
+          static_cast<const MediaContentDescription*>(content->description);
+      if (media->type() == media_type) {
+        return &*content;
+      }
+    }
+  }
+  return NULL;
+}
+
+const ContentInfo* GetFirstAudioContent(const SessionDescription* sdesc) {
+  return GetFirstMediaContent(sdesc, MEDIA_TYPE_AUDIO);
+}
+
+const ContentInfo* GetFirstVideoContent(const SessionDescription* sdesc) {
+  return GetFirstMediaContent(sdesc, MEDIA_TYPE_VIDEO);
+}
+
+// For video support only 80-bit SHA1 HMAC. For audio 32-bit HMAC is
+// tolerated because it is low overhead. Pick the crypto in the list
+// that is supported.
+bool SelectCrypto(const MediaContentDescription* offer, CryptoParams *crypto) {
+  bool audio = offer->type() == MEDIA_TYPE_AUDIO;
+  const CryptoParamsVec& cryptos = offer->cryptos();
+
+  for (CryptoParamsVec::const_iterator i = cryptos.begin();
+       i != cryptos.end(); ++i) {
+    if (CS_AES_CM_128_HMAC_SHA1_80 == i->cipher_suite ||
+        (CS_AES_CM_128_HMAC_SHA1_32 == i->cipher_suite && audio)) {
+      return CreateCryptoParams(i->tag, i->cipher_suite, crypto);
+    }
+  }
+  return false;
+}
+
+SessionDescription* MediaSessionClient::CreateAnswer(
+    const SessionDescription* offer, const CallOptions& options) {
+  // The answer contains the intersection of the codecs in the offer with the
+  // codecs we support, ordered by our local preference. As indicated by
+  // XEP-0167, we retain the same payload ids from the offer in the answer.
+  SessionDescription* accept = new SessionDescription();
+
+  const ContentInfo* audio_content = GetFirstAudioContent(offer);
+  if (audio_content) {
+    const AudioContentDescription* audio_offer =
+        static_cast<const AudioContentDescription*>(audio_content->description);
+    AudioContentDescription* audio_accept = new AudioContentDescription();
+    AudioCodecs audio_codecs;
+    channel_manager_->GetSupportedAudioCodecs(&audio_codecs);
+    for (AudioCodecs::const_iterator ours = audio_codecs.begin();
+        ours != audio_codecs.end(); ++ours) {
+      for (AudioCodecs::const_iterator theirs = audio_offer->codecs().begin();
+          theirs != audio_offer->codecs().end(); ++theirs) {
+        if (ours->Matches(*theirs)) {
+          AudioCodec negotiated(*ours);
+          negotiated.id = theirs->id;
+          audio_accept->AddCodec(negotiated);
+        }
+      }
+    }
+
+    audio_accept->SortCodecs();
+
+    if (secure() != SEC_DISABLED) {
+      CryptoParams crypto;
+
+      if (SelectCrypto(audio_offer, &crypto)) {
+        audio_accept->AddCrypto(crypto);
+      }
+    }
+
+    if (audio_accept->cryptos().empty() &&
+        (audio_offer->crypto_required() || secure() == SEC_REQUIRED)) {
+      return NULL;  // Fails the session setup.
+    }
+    accept->AddContent(audio_content->name, audio_content->type, audio_accept);
+  }
+
+  const ContentInfo* video_content = GetFirstVideoContent(offer);
+  if (video_content) {
+    const VideoContentDescription* video_offer =
+        static_cast<const VideoContentDescription*>(video_content->description);
+    VideoContentDescription* video_accept = new VideoContentDescription();
+    VideoCodecs video_codecs;
+    channel_manager_->GetSupportedVideoCodecs(&video_codecs);
+    for (VideoCodecs::const_iterator ours = video_codecs.begin();
+        ours != video_codecs.end(); ++ours) {
+      for (VideoCodecs::const_iterator theirs = video_offer->codecs().begin();
+          theirs != video_offer->codecs().end(); ++theirs) {
+        if (ours->Matches(*theirs)) {
+          VideoCodec negotiated(*ours);
+          negotiated.id = theirs->id;
+          video_accept->AddCodec(negotiated);
+        }
+      }
+    }
+
+    video_accept->set_bandwidth(options.video_bandwidth);
+    video_accept->SortCodecs();
+
+    if (secure() != SEC_DISABLED) {
+      CryptoParams crypto;
+
+      if (SelectCrypto(video_offer, &crypto)) {
+        video_accept->AddCrypto(crypto);
+      }
+    }
+
+    if (video_accept->cryptos().empty() &&
+        (video_offer->crypto_required() || secure() == SEC_REQUIRED)) {
+      return NULL;  // Fails the session setup.
+    }
+    accept->AddContent(video_content->name, video_content->type, video_accept);
+  }
+
+  return accept;
+}
+
+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);
+    const AudioContentDescription* audio_accept = (!audio_content) ? NULL :
+        static_cast<const AudioContentDescription*>(audio_content->description);
+
+    // For some reason, we need to create the call even when we
+    // reject.
+    Call *call = CreateCall();
+    session_map_[session->id()] = call;
+    call->IncomingSession(session, offer);
+
+    if (!audio_accept || audio_accept->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
+
+  std::map<std::string, Call *>::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) {
+  const std::string& type = NS_JINGLE_RTP;
+  Session *session = session_manager_->CreateSession(jid().Str(), type);
+  session_map_[session->id()] = call;
+  return session;
+}
+
+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;
+}
+
+void ParseGingleSsrc(const buzz::XmlElement* parent_elem,
+                     const buzz::QName& name,
+                     MediaContentDescription* content) {
+  const buzz::XmlElement* ssrc_elem = parent_elem->FirstNamed(name);
+  if (ssrc_elem) {
+    content->set_ssrc(strtoul(ssrc_elem->BodyText().c_str(), NULL, 10));
+  }
+}
+
+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) {
+      media->set_crypto_required(
+          GetXmlAttr(encryption, QN_ENCRYPTION_REQUIRED, false));
+      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;
+  if (bw_elem && FromString(bw_elem->BodyText(), &bandwidth_kbps)) {
+    if (bandwidth_kbps >= 0) {
+      media->set_bandwidth(bandwidth_kbps * 1000);
+    }
+  }
+}
+
+bool ParseGingleAudioContent(const buzz::XmlElement* content_elem,
+                             const ContentDescription** content,
+                             ParseError* error) {
+  AudioContentDescription* audio = new AudioContentDescription();
+
+  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)) {
+        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,
+                             const ContentDescription** content,
+                             ParseError* error) {
+  VideoContentDescription* video = new VideoContentDescription();
+
+  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)) {
+      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));
+    }
+  }
+}
+
+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;
+  }
+
+  media->set_crypto_required(
+      GetXmlAttr(encryption, QN_ENCRYPTION_REQUIRED, false));
+
+  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);
+  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);
+  return true;
+}
+
+bool ParseJingleAudioContent(const buzz::XmlElement* content_elem,
+                             const ContentDescription** content,
+                             ParseError* error) {
+  AudioContentDescription* audio = new AudioContentDescription();
+
+  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)) {
+      audio->AddCodec(codec);
+    }
+  }
+
+  if (!ParseJingleEncryption(content_elem, audio, error)) {
+    return false;
+  }
+  // TODO: Figure out how to integrate SSRC into Jingle.
+  *content = audio;
+  return true;
+}
+
+bool ParseJingleVideoContent(const buzz::XmlElement* content_elem,
+                             const ContentDescription** content,
+                             ParseError* error) {
+  VideoContentDescription* video = new VideoContentDescription();
+
+  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)) {
+      video->AddCodec(codec);
+    }
+  }
+
+  ParseBandwidth(content_elem, video);
+
+  if (!ParseJingleEncryption(content_elem, video, error)) {
+    return false;
+  }
+  // TODO: Figure out how to integrate SSRC into Jingle.
+  *content = video;
+  return true;
+}
+
+bool MediaSessionClient::ParseContent(SignalingProtocol protocol,
+                                     const buzz::XmlElement* content_elem,
+                                     const 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 {
+    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 {
+      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->ssrc_set()) {
+    elem->AddElement(CreateGingleSsrcElem(
+        QN_GINGLE_AUDIO_SRCID, audio->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->ssrc_set()) {
+    elem->AddElement(CreateGingleSsrcElem(
+        QN_GINGLE_VIDEO_SRCID, video->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;
+}
+
+buzz::XmlElement* CreatePayloadTypeParameterElem(
+    const std::string& name, int 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;
+}
+
+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);
+  }
+
+  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));
+
+  return 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);
+
+  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));
+  }
+
+  // TODO: Figure out how to integrate SSRC into Jingle.
+  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);
+
+  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->bandwidth() != kAutoBandwidth) {
+    elem->AddElement(CreateBandwidthElem(QN_JINGLE_RTP_BANDWIDTH,
+                                         video->bandwidth()));
+  }
+
+  // TODO: Figure out how to integrate SSRC into Jingle.
+  return elem;
+}
+
+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 {
+    return BadWrite("Unknown content type: " + media->type(), error);
+  }
+
+  return true;
+}
+
+}  // namespace cricket
diff --git a/talk/session/phone/mediasessionclient.h b/talk/session/phone/mediasessionclient.h
new file mode 100644
index 0000000..a74fe3e
--- /dev/null
+++ b/talk/session/phone/mediasessionclient.h
@@ -0,0 +1,274 @@
+/*
+ * 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_SESSION_PHONE_MEDIASESSIONCLIENT_H_
+#define TALK_SESSION_PHONE_MEDIASESSIONCLIENT_H_
+
+#include <string>
+#include <vector>
+#include <map>
+#include <algorithm>
+#include "talk/session/phone/call.h"
+#include "talk/session/phone/channelmanager.h"
+#include "talk/session/phone/cryptoparams.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/sigslotrepeater.h"
+#include "talk/base/messagequeue.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/sessionmanager.h"
+#include "talk/p2p/base/session.h"
+#include "talk/p2p/base/sessionclient.h"
+#include "talk/p2p/base/sessiondescription.h"
+
+namespace cricket {
+
+class Call;
+class SessionDescription;
+typedef std::vector<AudioCodec> AudioCodecs;
+typedef std::vector<VideoCodec> VideoCodecs;
+
+// SEC_ENABLED and SEC_REQUIRED should only be used if the session
+// was negotiated over TLS, to protect the inline crypto material
+// exchange.
+// SEC_DISABLED: No crypto in outgoing offer and answer. Fail any
+//               offer with crypto required.
+// SEC_ENABLED: Crypto in outgoing offer and answer. Fail any offer
+//              with unsupported required crypto. Crypto set but not
+//              required in outgoing offer.
+// SEC_REQUIRED: Crypto in outgoing offer and answer with
+//               required='true'. Fail any offer with no or
+//               unsupported crypto (implicit crypto required='true'
+//               in the offer.)
+enum SecureMediaPolicy {SEC_DISABLED, SEC_ENABLED, SEC_REQUIRED};
+
+const int kAutoBandwidth = -1;
+
+struct CallOptions {
+  CallOptions() :
+      is_video(false),
+      is_muc(false),
+      video_bandwidth(kAutoBandwidth) {
+  }
+
+  bool is_video;
+  bool is_muc;
+  // bps. -1 == auto.
+  int video_bandwidth;
+};
+
+class MediaSessionClient: public SessionClient, public sigslot::has_slots<> {
+ public:
+
+  MediaSessionClient(const buzz::Jid& jid, SessionManager *manager);
+  // Alternative constructor, allowing injection of media_engine
+  // and device_manager.
+  MediaSessionClient(const buzz::Jid& jid, SessionManager *manager,
+      MediaEngine* media_engine, DeviceManager* device_manager);
+  ~MediaSessionClient();
+
+  const buzz::Jid &jid() const { return jid_; }
+  SessionManager* session_manager() const { return session_manager_; }
+  ChannelManager* channel_manager() const { return channel_manager_; }
+
+  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,
+                       int opts) {
+    return channel_manager_->SetAudioOptions(in_name, out_name, opts);
+  }
+  bool SetOutputVolume(int level) {
+    return channel_manager_->SetOutputVolume(level);
+  }
+  bool SetVideoOptions(const std::string& cam_device) {
+    return channel_manager_->SetVideoOptions(cam_device);
+  }
+
+  sigslot::signal2<Call *, Call *> SignalFocus;
+  sigslot::signal1<Call *> SignalCallCreate;
+  sigslot::signal1<Call *> SignalCallDestroy;
+  sigslot::repeater0<> SignalDevicesChange;
+
+  SessionDescription* CreateOffer(const CallOptions& options);
+  SessionDescription* CreateAnswer(const SessionDescription* offer,
+                                   const CallOptions& options);
+
+  SecureMediaPolicy secure() const { return secure_; }
+  void set_secure(SecureMediaPolicy s) { secure_ = s; }
+
+ private:
+  void Construct();
+  void OnSessionCreate(Session *session, bool received_initiate);
+  void OnSessionState(BaseSession *session, BaseSession::State state);
+  void OnSessionDestroy(Session *session);
+  virtual bool ParseContent(SignalingProtocol protocol,
+                            const buzz::XmlElement* elem,
+                            const ContentDescription** content,
+                            ParseError* error);
+  virtual bool WriteContent(SignalingProtocol protocol,
+                            const ContentDescription* content,
+                            buzz::XmlElement** elem,
+                            WriteError* error);
+  Session *CreateSession(Call *call);
+
+  buzz::Jid jid_;
+  SessionManager* session_manager_;
+  Call *focus_call_;
+  ChannelManager *channel_manager_;
+  std::map<uint32, Call *> calls_;
+  std::map<std::string, Call *> session_map_;
+  SecureMediaPolicy secure_;
+  friend class Call;
+};
+
+enum MediaType {
+  MEDIA_TYPE_AUDIO,
+  MEDIA_TYPE_VIDEO
+};
+
+class MediaContentDescription : public ContentDescription {
+ public:
+  MediaContentDescription()
+      : ssrc_(0),
+        ssrc_set_(false),
+        rtcp_mux_(false), 
+        rtp_headers_disabled_(false),
+        crypto_required_(false),
+        bandwidth_(kAutoBandwidth) {
+  }
+
+  virtual MediaType type() const = 0;
+
+  uint32 ssrc() const { return ssrc_; }
+  bool ssrc_set() const { return ssrc_set_; }
+  void set_ssrc(uint32 ssrc) {
+    ssrc_ = ssrc;
+    ssrc_set_ = true;
+  }
+
+  bool rtcp_mux() const { return rtcp_mux_; }
+  void set_rtcp_mux(bool mux) { rtcp_mux_ = mux; }
+
+  bool rtp_headers_disabled() const {
+    return rtp_headers_disabled_;
+  }
+  void set_rtp_headers_disabled(bool disable) {
+    rtp_headers_disabled_ = disable;
+  }
+
+  const std::vector<CryptoParams>& cryptos() const { return cryptos_; }
+  void AddCrypto(const CryptoParams& params) {
+    cryptos_.push_back(params);
+  }
+  bool crypto_required() const { return crypto_required_; }
+  void set_crypto_required(bool crypto) {
+    crypto_required_ = crypto;
+  }
+
+  int bandwidth() const { return bandwidth_; }
+  void set_bandwidth(int bandwidth) { bandwidth_ = bandwidth; }
+
+ protected:
+  uint32 ssrc_;
+  bool ssrc_set_;
+  bool rtcp_mux_;
+  bool rtp_headers_disabled_;
+  std::vector<CryptoParams> cryptos_;
+  bool crypto_required_;
+  int bandwidth_;
+};
+
+template <class C>
+class MediaContentDescriptionImpl : public MediaContentDescription {
+ public:
+  struct PreferenceSort {
+    bool operator()(C a, C b) { return a.preference > b.preference; }
+  };
+
+  const std::vector<C>& codecs() const { return codecs_; }
+  void AddCodec(const C& codec) {
+    codecs_.push_back(codec);
+  }
+  void SortCodecs() {
+    std::sort(codecs_.begin(), codecs_.end(), PreferenceSort());
+  }
+
+ private:
+  std::vector<C> codecs_;
+};
+
+class AudioContentDescription : public MediaContentDescriptionImpl<AudioCodec> {
+ public:
+  AudioContentDescription() :
+      conference_mode_(false) {}
+
+  virtual MediaType type() const { return MEDIA_TYPE_AUDIO; }
+
+  bool conference_mode() const { return conference_mode_; }
+  void set_conference_mode(bool enable) {
+    conference_mode_ = enable;
+  }
+
+  const std::string &lang() const { return lang_; }
+  void set_lang(const std::string &lang) { lang_ = lang; }
+
+
+ private:
+  bool conference_mode_;
+  std::string lang_;
+};
+
+class VideoContentDescription : public MediaContentDescriptionImpl<VideoCodec> {
+ public:
+  virtual MediaType type() const { return MEDIA_TYPE_VIDEO; }
+};
+
+// Convenience functions.
+const ContentInfo* GetFirstAudioContent(const SessionDescription* sdesc);
+const ContentInfo* GetFirstVideoContent(const SessionDescription* sdesc);
+
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_MEDIASESSIONCLIENT_H_
diff --git a/talk/session/phone/mediasink.h b/talk/session/phone/mediasink.h
new file mode 100644
index 0000000..078b534
--- /dev/null
+++ b/talk/session/phone/mediasink.h
@@ -0,0 +1,49 @@
+/*
+ * libjingle
+ * Copyright 2004--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.
+ */
+
+#ifndef TALK_SESSION_PHONE_MEDIASINK_H_
+#define TALK_SESSION_PHONE_MEDIASINK_H_
+
+namespace cricket {
+
+// MediaSinkInterface is a sink to handle RTP and RTCP packets that are sent or
+// received by a channel. Each channel needs two MediaSinkInterface, one for
+// the sent packets and the other for the received packets.
+class MediaSinkInterface {
+ public:
+  virtual ~MediaSinkInterface() {}
+
+  virtual void SetMaxSize(size_t size) = 0;
+  virtual bool Enable(bool enable) = 0;
+  virtual bool IsEnabled() const = 0;
+  virtual void OnRtpPacket(const void* data, size_t size) = 0;
+  virtual void OnRtcpPacket(const void* data, size_t size) = 0;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_MEDIASINK_H_
diff --git a/talk/session/phone/rtcpmuxfilter.cc b/talk/session/phone/rtcpmuxfilter.cc
new file mode 100644
index 0000000..8654214
--- /dev/null
+++ b/talk/session/phone/rtcpmuxfilter.cc
@@ -0,0 +1,92 @@
+/*
+ * libjingle
+ * Copyright 2004--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 "talk/session/phone/rtcpmuxfilter.h"
+
+#include "talk/base/logging.h"
+
+namespace cricket {
+
+RtcpMuxFilter::RtcpMuxFilter() : state_(ST_INIT), offer_enable_(false) {
+}
+
+bool RtcpMuxFilter::IsActive() const {
+  // We can receive muxed media prior to the accept, so we have to be able to
+  // deal with that.
+  return (state_ == ST_SENTOFFER || state_ == ST_ACTIVE);
+}
+
+bool RtcpMuxFilter::SetOffer(bool offer_enable, ContentSource source) {
+  bool ret = false;
+  if (state_ == ST_INIT) {
+    offer_enable_ = offer_enable;
+    state_ = (source == CS_LOCAL) ? ST_SENTOFFER : ST_RECEIVEDOFFER;
+    ret = true;
+  } else {
+    LOG(LS_ERROR) << "Invalid state for RTCP mux offer";
+  }
+  return ret;
+}
+
+bool RtcpMuxFilter::SetAnswer(bool answer_enable, ContentSource source) {
+  bool ret = false;
+  if ((state_ == ST_SENTOFFER && source == CS_REMOTE) ||
+      (state_ == ST_RECEIVEDOFFER && source == CS_LOCAL)) {
+    if (offer_enable_) {
+      state_ = (answer_enable) ? ST_ACTIVE : ST_INIT;
+      ret = true;
+    } else {
+      // If the offer didn't specify RTCP mux, the answer shouldn't either.
+      if (!answer_enable) {
+        ret = true;
+        state_ = ST_INIT;
+      } else {
+        LOG(LS_WARNING) << "Invalid parameters in RTCP mux answer";
+      }
+    }
+  } else {
+    LOG(LS_ERROR) << "Invalid state for RTCP mux answer";
+  }
+  return ret;
+}
+
+bool RtcpMuxFilter::DemuxRtcp(const char* data, int len) {
+  // If we're muxing RTP/RTCP, we must inspect each packet delivered and
+  // determine whether it is RTP or RTCP. We do so by checking the packet type,
+  // and assuming RTP if type is 0-63 or 96-127. For additional details, see
+  // http://tools.ietf.org/html/rfc5761.
+  // Note that if we offer RTCP mux, we may receive muxed RTCP before we
+  // receive the answer, so we operate in that state too.
+  if (!IsActive()) {
+    return false;
+  }
+
+  int type = (len >= 2) ? (static_cast<uint8>(data[1]) & 0x7F) : 0;
+  return (type >= 64 && type < 96);
+}
+
+}  // namespace cricket
diff --git a/talk/session/phone/rtcpmuxfilter.h b/talk/session/phone/rtcpmuxfilter.h
new file mode 100644
index 0000000..0224e9f
--- /dev/null
+++ b/talk/session/phone/rtcpmuxfilter.h
@@ -0,0 +1,61 @@
+/*
+ * libjingle
+ * Copyright 2004--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.
+ */
+
+#ifndef TALK_SESSION_PHONE_RTCPMUXFILTER_H_
+#define TALK_SESSION_PHONE_RTCPMUXFILTER_H_
+
+#include "talk/base/basictypes.h"
+#include "talk/p2p/base/sessiondescription.h"
+
+namespace cricket {
+
+// RTCP Muxer, as defined in RFC 5761 (http://tools.ietf.org/html/rfc5761)
+class RtcpMuxFilter {
+ public:
+  RtcpMuxFilter();
+
+  // Whether the filter is active, i.e. has RTCP mux been properly negotiated.
+  bool IsActive() const;
+
+  // Specifies whether the offer indicates the use of RTCP mux.
+  bool SetOffer(bool offer_enable, ContentSource src);
+
+  // Specifies whether the answer indicates the use of RTCP mux.
+  bool SetAnswer(bool answer_enable, ContentSource src);
+
+  // Determines whether the specified packet is RTCP.
+  bool DemuxRtcp(const char* data, int len);
+
+ private:
+  enum State { ST_INIT, ST_SENTOFFER, ST_RECEIVEDOFFER, ST_ACTIVE };
+  State state_;
+  bool offer_enable_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_RTCPMUXFILTER_H_
diff --git a/talk/session/phone/rtpdump.cc b/talk/session/phone/rtpdump.cc
new file mode 100644
index 0000000..37d664b
--- /dev/null
+++ b/talk/session/phone/rtpdump.cc
@@ -0,0 +1,336 @@
+/*
+ * 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 "talk/session/phone/rtpdump.h"
+
+#include <string>
+
+#include "talk/base/bytebuffer.h"
+#include "talk/base/byteorder.h"
+#include "talk/base/logging.h"
+#include "talk/base/time.h"
+
+namespace cricket {
+
+const std::string RtpDumpFileHeader::kFirstLine =
+    "#!rtpplay1.0 0.0.0.0/0\n";
+
+RtpDumpFileHeader::RtpDumpFileHeader(uint32 start_ms, uint32 s, uint16 p)
+    : start_sec(start_ms / 1000),
+      start_usec(start_ms % 1000 * 1000),
+      source(s),
+      port(p),
+      padding(0) {
+}
+
+void RtpDumpFileHeader::WriteToByteBuffer(talk_base::ByteBuffer* buf) {
+  buf->WriteUInt32(start_sec);
+  buf->WriteUInt32(start_usec);
+  buf->WriteUInt32(source);
+  buf->WriteUInt16(port);
+  buf->WriteUInt16(padding);
+}
+
+// RTP packet format (http://www.networksorcery.com/enp/protocol/rtp.htm).
+static const size_t kMinimumRtpHeaderSize = 12;
+static const uint32 kDefaultTimeIncrease = 30;
+
+bool RtpDumpPacket::IsValidRtpPacket() const {
+  return !is_rtcp && data.size() >= kMinimumRtpHeaderSize;
+}
+
+bool RtpDumpPacket::GetRtpSeqNum(uint16* seq_num) const {
+  if (!seq_num || !IsValidRtpPacket()) {
+    return false;
+  }
+  *seq_num = talk_base::GetBE16(&data[2]);
+  return true;
+}
+
+bool RtpDumpPacket::GetRtpTimestamp(uint32* ts) const {
+  if (!ts || !IsValidRtpPacket()) {
+    return false;
+  }
+  *ts = talk_base::GetBE32(&data[4]);
+  return true;
+}
+
+bool RtpDumpPacket::GetRtpSsrc(uint32* ssrc) const {
+  if (!ssrc || !IsValidRtpPacket()) {
+    return false;
+  }
+  *ssrc = talk_base::GetBE32(&data[8]);
+  return true;
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Implementation of RtpDumpReader.
+///////////////////////////////////////////////////////////////////////////
+talk_base::StreamResult RtpDumpReader::ReadPacket(RtpDumpPacket* packet) {
+  if (!packet) return talk_base::SR_ERROR;
+
+  talk_base::StreamResult res = talk_base::SR_SUCCESS;
+  // Read the file header if it has not been read yet.
+  if (!file_header_read_) {
+    res = ReadFileHeader();
+    if (res != talk_base::SR_SUCCESS) {
+      return res;
+    }
+    file_header_read_ = true;
+  }
+
+  // Read the RTP dump packet header.
+  char header[RtpDumpPacket::kHeaderLength];
+  res = stream_->ReadAll(header, sizeof(header), NULL, NULL);
+  if (res != talk_base::SR_SUCCESS) {
+    return res;
+  }
+  talk_base::ByteBuffer buf(header, sizeof(header));
+  uint16 dump_packet_len;
+  uint16 data_len;
+  buf.ReadUInt16(&dump_packet_len);
+  buf.ReadUInt16(&data_len);  // data.size() for RTP, 0 for RTCP.
+  packet->is_rtcp = (0 == data_len);
+  buf.ReadUInt32(&packet->elapsed_time);
+  packet->data.resize(dump_packet_len - sizeof(header));
+
+  // Read the actual RTP or RTCP packet.
+  return stream_->ReadAll(&packet->data[0], packet->data.size(), NULL, NULL);
+}
+
+talk_base::StreamResult RtpDumpReader::ReadFileHeader() {
+  // Read the first line.
+  std::string first_line;
+  talk_base::StreamResult res = stream_->ReadLine(&first_line);
+  if (res != talk_base::SR_SUCCESS) {
+    return res;
+  }
+  if (!CheckFirstLine(first_line)) {
+    return talk_base::SR_ERROR;
+  }
+
+  // Read the 16 byte file header.
+  char header[RtpDumpFileHeader::kHeaderLength];
+  res = stream_->ReadAll(header, sizeof(header), NULL, NULL);
+  if (res == talk_base::SR_SUCCESS) {
+    talk_base::ByteBuffer buf(header, sizeof(header));
+    uint32 start_sec;
+    uint32 start_usec;
+    buf.ReadUInt32(&start_sec);
+    buf.ReadUInt32(&start_usec);
+    start_time_ms_ = start_sec * 1000 + start_usec / 1000;
+    // Increase the length by 1 since first_line does not contain the ending \n.
+    first_line_and_file_header_len_ = first_line.size() + 1 + sizeof(header);
+  }
+  return res;
+}
+
+bool RtpDumpReader::CheckFirstLine(const std::string& first_line) {
+  // The first line is like "#!rtpplay1.0 address/port"
+  bool matched = (0 == first_line.find("#!rtpplay1.0 "));
+
+  // The address could be IP or hostname. We do not check it here. Instead, we
+  // check the port at the end.
+  size_t pos = first_line.find('/');
+  matched &= (pos != std::string::npos && pos < first_line.size() - 1);
+  for (++pos; pos < first_line.size() && matched; ++pos) {
+    matched &= (0 != isdigit(first_line[pos]));
+  }
+
+  return matched;
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Implementation of RtpDumpLoopReader.
+///////////////////////////////////////////////////////////////////////////
+RtpDumpLoopReader::RtpDumpLoopReader(talk_base::StreamInterface* stream)
+    : RtpDumpReader(stream),
+      loop_count_(0),
+      elapsed_time_increases_(0),
+      rtp_seq_num_increase_(0),
+      rtp_timestamp_increase_(0),
+      packet_count_(0),
+      frame_count_(0),
+      first_elapsed_time_(0),
+      first_rtp_seq_num_(0),
+      first_rtp_timestamp_(0),
+      prev_elapsed_time_(0),
+      prev_rtp_seq_num_(0),
+      prev_rtp_timestamp_(0) {
+}
+
+talk_base::StreamResult RtpDumpLoopReader::ReadPacket(RtpDumpPacket* packet) {
+  if (!packet) return talk_base::SR_ERROR;
+
+  talk_base::StreamResult res = RtpDumpReader::ReadPacket(packet);
+  if (talk_base::SR_SUCCESS == res) {
+    if (0 == loop_count_) {
+      // During the first loop, we update the statistics of the input stream.
+      UpdateStreamStatistics(*packet);
+    }
+  } else if (talk_base::SR_EOS == res) {
+    if (0 == loop_count_) {
+      // At the end of the first loop, calculate elapsed_time_increases_,
+      // rtp_seq_num_increase_, and rtp_timestamp_increase_, which will be
+      // used during the second and later loops.
+      CalculateIncreases();
+    }
+
+    // Rewind the input stream to the first dump packet and read again.
+    ++loop_count_;
+    if (RewindToFirstDumpPacket()) {
+      res = RtpDumpReader::ReadPacket(packet);
+    }
+  }
+
+  if (talk_base::SR_SUCCESS == res && loop_count_ > 0) {
+    // During the second and later loops, we update the elapsed time of the dump
+    // packet. If the dumped packet is a RTP packet, we also update its RTP
+    // sequence number and timestamp.
+    UpdateDumpPacket(packet);
+  }
+
+  return res;
+}
+
+void RtpDumpLoopReader::UpdateStreamStatistics(const RtpDumpPacket& packet) {
+  // Get the RTP sequence number and timestamp of the dump packet.
+  uint16 rtp_seq_num = 0;
+  packet.GetRtpSeqNum(&rtp_seq_num);
+  uint32 rtp_timestamp = 0;
+  packet.GetRtpTimestamp(&rtp_timestamp);
+
+  // Set the timestamps and sequence number for the first dump packet.
+  if (0 == packet_count_++) {
+    first_elapsed_time_ = packet.elapsed_time;
+    first_rtp_seq_num_ = rtp_seq_num;
+    first_rtp_timestamp_ = rtp_timestamp;
+    // The first packet belongs to a new payload frame.
+    ++frame_count_;
+  } else if (rtp_timestamp != prev_rtp_timestamp_) {
+    // The current and previous packets belong to different payload frames.
+    ++frame_count_;
+  }
+
+  prev_elapsed_time_ = packet.elapsed_time;
+  prev_rtp_timestamp_ = rtp_timestamp;
+  prev_rtp_seq_num_ = rtp_seq_num;
+}
+
+void RtpDumpLoopReader::CalculateIncreases() {
+  // At this time, prev_elapsed_time_, prev_rtp_seq_num_, and
+  // prev_rtp_timestamp_ are values of the last dump packet in the input stream.
+  rtp_seq_num_increase_ = prev_rtp_seq_num_ - first_rtp_seq_num_ + 1;
+  // If we have only one packet or frame, we use the default timestamp
+  // increase. Otherwise, we use the difference between the first and the last
+  // packets or frames.
+  elapsed_time_increases_ = packet_count_ <= 1 ? kDefaultTimeIncrease :
+      (prev_elapsed_time_ - first_elapsed_time_) * packet_count_ /
+      (packet_count_ - 1);
+  rtp_timestamp_increase_ = frame_count_ <= 1 ? kDefaultTimeIncrease :
+      (prev_rtp_timestamp_ - first_rtp_timestamp_) * frame_count_ /
+      (frame_count_ - 1);
+}
+
+void RtpDumpLoopReader::UpdateDumpPacket(RtpDumpPacket* packet) {
+  // Increase the elapsed time of the dump packet.
+  packet->elapsed_time += loop_count_ * elapsed_time_increases_;
+
+  if (packet->IsValidRtpPacket()) {
+    // Get the old RTP sequence number and timestamp.
+    uint16 sequence = 0;
+    packet->GetRtpSeqNum(&sequence);
+    uint32 timestamp = 0;
+    packet->GetRtpTimestamp(&timestamp);
+    // Increase the RTP sequence number and timestamp.
+    sequence += loop_count_ * rtp_seq_num_increase_;
+    timestamp += loop_count_ * rtp_timestamp_increase_;
+    // Write the updated sequence number and timestamp back to the RTP packet.
+    talk_base::ByteBuffer buffer;
+    buffer.WriteUInt16(sequence);
+    buffer.WriteUInt32(timestamp);
+    memcpy(&packet->data[2], buffer.Data(), buffer.Length());
+  }
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Implementation of RtpDumpWriter.
+///////////////////////////////////////////////////////////////////////////
+
+RtpDumpWriter::RtpDumpWriter(talk_base::StreamInterface* stream)
+    : stream_(stream),
+      file_header_written_(false),
+      start_time_ms_(talk_base::Time()) {
+  }
+
+uint32 RtpDumpWriter::GetElapsedTime() const {
+  return talk_base::TimeSince(start_time_ms_);
+}
+
+talk_base::StreamResult RtpDumpWriter::WritePacket(
+    const void* data, size_t data_len, uint32 elapsed, bool rtcp) {
+  if (!stream_ || !data || 0 == data_len) return talk_base::SR_ERROR;
+
+  talk_base::StreamResult res = talk_base::SR_SUCCESS;
+  // Write the file header if it has not been written yet.
+  if (!file_header_written_) {
+    res = WriteFileHeader();
+    if (res != talk_base::SR_SUCCESS) {
+      return res;
+    }
+    file_header_written_ = true;
+  }
+
+  // Write the dump packet header.
+  talk_base::ByteBuffer buf;
+  buf.WriteUInt16(static_cast<uint16>(RtpDumpPacket::kHeaderLength + data_len));
+  buf.WriteUInt16(static_cast<uint16>(rtcp ? 0 : data_len));
+  buf.WriteUInt32(elapsed);
+  res = stream_->WriteAll(buf.Data(), buf.Length(), NULL, NULL);
+  if (res != talk_base::SR_SUCCESS) {
+    return res;
+  }
+
+  // Write the actual RTP or RTCP packet.
+  return stream_->WriteAll(data, data_len, NULL, NULL);
+}
+
+talk_base::StreamResult RtpDumpWriter::WriteFileHeader() {
+  talk_base::StreamResult res = stream_->WriteAll(
+      RtpDumpFileHeader::kFirstLine.c_str(),
+      RtpDumpFileHeader::kFirstLine.size(), NULL, NULL);
+  if (res != talk_base::SR_SUCCESS) {
+    return res;
+  }
+
+  talk_base::ByteBuffer buf;
+  RtpDumpFileHeader file_header(talk_base::Time(), 0, 0);
+  file_header.WriteToByteBuffer(&buf);
+  return stream_->WriteAll(buf.Data(), buf.Length(), NULL, NULL);
+}
+
+}  // namespace cricket
diff --git a/talk/session/phone/rtpdump.h b/talk/session/phone/rtpdump.h
new file mode 100644
index 0000000..f87b922
--- /dev/null
+++ b/talk/session/phone/rtpdump.h
@@ -0,0 +1,203 @@
+/*
+ * 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.
+ */
+
+#ifndef TALK_SESSION_PHONE_RTPDUMP_H_
+#define TALK_SESSION_PHONE_RTPDUMP_H_
+
+#include <cstring>
+#include <string>
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/stream.h"
+
+namespace talk_base {
+class ByteBuffer;
+}
+
+namespace cricket {
+
+// We use the RTP dump file format compatible to the format used by rtptools
+// (http://www.cs.columbia.edu/irt/software/rtptools/) and Wireshark
+// (http://wiki.wireshark.org/rtpdump). In particular, the file starts with the
+// first line "#!rtpplay1.0 address/port\n", followed by a 16 byte file header.
+// For each packet, the file contains a 8 byte dump packet header, followed by
+// the actual RTP or RTCP packet.
+
+struct RtpDumpFileHeader {
+  RtpDumpFileHeader(uint32 start_ms, uint32 s, uint16 p);
+  void WriteToByteBuffer(talk_base::ByteBuffer* buf);
+
+  static const std::string kFirstLine;
+  static const size_t kHeaderLength = 16;
+  uint32 start_sec;   // start of recording, the seconds part.
+  uint32 start_usec;  // start of recording, the microseconds part.
+  uint32 source;      // network source (multicast address).
+  uint16 port;        // UDP port.
+  uint16 padding;     // 2 bytes padding.
+};
+
+struct RtpDumpPacket {
+  RtpDumpPacket() {}
+
+  RtpDumpPacket(const void* d, size_t s, uint32 elapsed, bool rtcp)
+      : elapsed_time(elapsed),
+        is_rtcp(rtcp) {
+    data.resize(s);
+    memcpy(&data[0], d, s);
+  }
+
+  bool IsValidRtpPacket() const;
+  // Get the sequence number, timestampe, and SSRC of the RTP packet. Return
+  // true and set the output parameter if successful.
+  bool GetRtpSeqNum(uint16* seq_num) const;
+  bool GetRtpTimestamp(uint32* ts) const;
+  bool GetRtpSsrc(uint32* ssrc) const;
+
+  static const size_t kHeaderLength = 8;
+  uint32 elapsed_time;      // Milliseconds since the start of recording.
+  bool is_rtcp;             // True if the data below is a RTCP packet.
+  std::vector<uint8> data;  // The actual RTP or RTCP packet.
+};
+
+class RtpDumpReader {
+ public:
+  explicit RtpDumpReader(talk_base::StreamInterface* stream)
+      : stream_(stream),
+        file_header_read_(false),
+        first_line_and_file_header_len_(0),
+        start_time_ms_(0) {
+  }
+  virtual ~RtpDumpReader() {}
+
+  virtual talk_base::StreamResult ReadPacket(RtpDumpPacket* packet);
+
+ protected:
+  talk_base::StreamResult ReadFileHeader();
+  bool RewindToFirstDumpPacket() {
+    return stream_->SetPosition(first_line_and_file_header_len_);
+  }
+
+ private:
+  // Check if its matches "#!rtpplay1.0 address/port\n".
+  bool CheckFirstLine(const std::string& first_line);
+
+  talk_base::StreamInterface* stream_;
+  bool file_header_read_;
+  size_t first_line_and_file_header_len_;
+  uint32 start_time_ms_;
+  DISALLOW_COPY_AND_ASSIGN(RtpDumpReader);
+};
+
+// RtpDumpLoopReader reads RTP dump packets from the input stream and rewinds
+// the stream when it ends. RtpDumpLoopReader maintains the elapsed time, the
+// RTP sequence number and the RTP timestamp properly. RtpDumpLoopReader can
+// handle both RTP dump and RTCP dump. We assume that the dump does not mix
+// RTP packets and RTCP packets.
+class RtpDumpLoopReader : public RtpDumpReader {
+ public:
+  explicit RtpDumpLoopReader(talk_base::StreamInterface* stream);
+  virtual talk_base::StreamResult ReadPacket(RtpDumpPacket* packet);
+
+ private:
+  // During the first loop, update the statistics, including packet count, frame
+  // count, timestamps, and sequence number, of the input stream.
+  void UpdateStreamStatistics(const RtpDumpPacket& packet);
+
+  // At the end of first loop, calculate elapsed_time_increases_,
+  // rtp_seq_num_increase_, and rtp_timestamp_increase_.
+  void CalculateIncreases();
+
+  // During the second and later loops, update the elapsed time of the dump
+  // packet. If the dumped packet is a RTP packet, update its RTP sequence
+  // number and timestamp as well.
+  void UpdateDumpPacket(RtpDumpPacket* packet);
+
+  int loop_count_;
+  // How much to increase the elapsed time, RTP sequence number, RTP timestampe
+  // for each loop. They are calcualted with the variables below during the
+  // first loop.
+  uint32 elapsed_time_increases_;
+  uint16 rtp_seq_num_increase_;
+  uint32 rtp_timestamp_increase_;
+  // How many RTP packets and how many payload frames in the input stream. RTP
+  // packets belong to the same frame have the same RTP timestamp, different
+  // dump timestamp, and different RTP sequence number.
+  uint32 packet_count_;
+  uint32 frame_count_;
+  // The elapsed time, RTP sequence number, and RTP timestamp of the first and
+  // the previous dump packets in the input stream.
+  uint32 first_elapsed_time_;
+  uint16 first_rtp_seq_num_;
+  uint32 first_rtp_timestamp_;
+  uint32 prev_elapsed_time_;
+  uint16 prev_rtp_seq_num_;
+  uint32 prev_rtp_timestamp_;
+
+  DISALLOW_COPY_AND_ASSIGN(RtpDumpLoopReader);
+};
+
+class RtpDumpWriter {
+ public:
+  explicit RtpDumpWriter(talk_base::StreamInterface* stream);
+
+  // Write a RTP or RTCP packet. The parameters data points to the packet and
+  // data_len is its length.
+  talk_base::StreamResult WriteRtpPacket(const void* data, size_t data_len) {
+    return WritePacket(data, data_len, GetElapsedTime(), false);
+  }
+  talk_base::StreamResult WriteRtcpPacket(const void* data, size_t data_len) {
+    return WritePacket(data, data_len, GetElapsedTime(), true);
+  }
+  talk_base::StreamResult WritePacket(const RtpDumpPacket& packet) {
+    return WritePacket(&packet.data[0], packet.data.size(), packet.elapsed_time,
+                       packet.is_rtcp);
+  }
+  uint32 GetElapsedTime() const;
+
+  bool GetDumpSize(size_t* size) {
+    // Note that we use GetPosition(), rather than GetSize(), to avoid flush the
+    // stream per write.
+    return stream_ && size && stream_->GetPosition(size);
+  }
+
+ protected:
+  talk_base::StreamResult WriteFileHeader();
+
+ private:
+  talk_base::StreamResult WritePacket(const void* data, size_t data_len,
+                                      uint32 elapsed, bool rtcp);
+
+  talk_base::StreamInterface* stream_;
+  bool file_header_written_;
+  uint32 start_time_ms_;  // Time when the record starts.
+  DISALLOW_COPY_AND_ASSIGN(RtpDumpWriter);
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_RTPDUMP_H_
diff --git a/talk/session/phone/soundclip.cc b/talk/session/phone/soundclip.cc
new file mode 100644
index 0000000..f1069e0
--- /dev/null
+++ b/talk/session/phone/soundclip.cc
@@ -0,0 +1,82 @@
+/*
+ * libjingle
+ * Copyright 2004--2009, 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/session/phone/soundclip.h"
+
+namespace cricket {
+
+enum {
+  MSG_PLAYSOUND = 1,
+};
+
+struct PlaySoundMessageData : talk_base::MessageData {
+  PlaySoundMessageData(const void *c,
+                       int l,
+                       SoundclipMedia::SoundclipFlags f) 
+      : clip(c),
+        len(l),
+        flags(f),
+        result(false) {
+  }
+
+  const void *clip;
+  int len;
+  SoundclipMedia::SoundclipFlags flags;
+  bool result;
+};
+
+Soundclip::Soundclip(talk_base::Thread *thread, SoundclipMedia *soundclip_media)
+    : worker_thread_(thread),
+      soundclip_media_(soundclip_media) {
+}
+
+bool Soundclip::PlaySound(const void *clip,
+                          int len,
+                          SoundclipMedia::SoundclipFlags flags) {
+  PlaySoundMessageData data(clip, len, flags);
+  worker_thread_->Send(this, MSG_PLAYSOUND, &data);
+  return data.result;
+}
+
+bool Soundclip::PlaySound_w(const void *clip,
+                            int len,
+                            SoundclipMedia::SoundclipFlags flags) {
+  return soundclip_media_->PlaySound(static_cast<const char *>(clip),
+                                     len,
+                                     flags);
+}
+
+void Soundclip::OnMessage(talk_base::Message *message) {
+  ASSERT(message->message_id == MSG_PLAYSOUND);
+  PlaySoundMessageData *data =
+      static_cast<PlaySoundMessageData *>(message->pdata);
+  data->result = PlaySound_w(data->clip,
+                             data->len,
+                             data->flags);
+}
+
+}  // namespace cricket
diff --git a/talk/session/phone/soundclip.h b/talk/session/phone/soundclip.h
new file mode 100644
index 0000000..4038477
--- /dev/null
+++ b/talk/session/phone/soundclip.h
@@ -0,0 +1,70 @@
+/*
+ * libjingle
+ * Copyright 2004--2009, 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_PHONE_SOUNDCLIP_H_
+#define TALK_SESSION_PHONE_SOUNDCLIP_H_
+
+#include "talk/base/scoped_ptr.h"
+#include "talk/session/phone/mediaengine.h"
+
+namespace talk_base {
+
+class Thread;
+
+}
+
+namespace cricket {
+
+// Soundclip wraps SoundclipMedia to support marshalling calls to the proper
+// thread.
+class Soundclip : private talk_base::MessageHandler {
+ public:
+  Soundclip(talk_base::Thread* thread, SoundclipMedia* soundclip_media);
+
+  // Plays a sound out to the speakers with the given audio stream. The stream
+  // must be 16-bit little-endian 16 kHz PCM. If a stream is already playing
+  // on this Soundclip, it is stopped. If clip is NULL, nothing is played.
+  // Returns whether it was successful.
+  bool PlaySound(const void* clip,
+                 int len,
+                 SoundclipMedia::SoundclipFlags flags);
+
+ private:
+  bool PlaySound_w(const void* clip,
+                   int len,
+                   SoundclipMedia::SoundclipFlags flags);
+
+  // From MessageHandler
+  virtual void OnMessage(talk_base::Message* message);
+
+  talk_base::Thread* worker_thread_;
+  talk_base::scoped_ptr<SoundclipMedia> soundclip_media_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_SOUNDCLIP_H_
diff --git a/talk/session/phone/srtpfilter.cc b/talk/session/phone/srtpfilter.cc
new file mode 100644
index 0000000..71f1991
--- /dev/null
+++ b/talk/session/phone/srtpfilter.cc
@@ -0,0 +1,493 @@
+/*
+ * libjingle
+ * Copyright 2009, 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.
+ */
+
+// talk's config.h, generated from mac_config_dot_h for OSX, conflicts with the
+// one included by the libsrtp headers. Don't use it. Instead, we keep HAVE_SRTP
+// and LOGGING defined in config.h.
+#undef HAVE_CONFIG_H
+
+#ifdef OSX
+// TODO: For the XCode build, we force SRTP (b/2500074)
+#ifndef HAVE_SRTP
+#define HAVE_SRTP 1
+#endif  // HAVE_SRTP
+// If LOGGING is not defined, define it to 1 (b/3245816)
+#ifndef LOGGING
+#define LOGGING 1
+#endif  // HAVE_SRTP
+#endif
+
+#include "talk/session/phone/srtpfilter.h"
+
+#include <algorithm>
+#include <cstring>
+
+#include "talk/base/base64.h"
+#include "talk/base/logging.h"
+
+// Enable this line to turn on SRTP debugging
+// #define SRTP_DEBUG
+
+#ifdef HAVE_SRTP
+#ifdef SRTP_RELATIVE_PATH
+#include "srtp.h"  // NOLINT
+#else
+#include "third_party/libsrtp/include/srtp.h"
+#endif  // SRTP_RELATIVE_PATH
+#ifdef _DEBUG
+extern "C" debug_module_t mod_srtp;
+#endif
+#else
+// SrtpFilter needs that constant.
+#define SRTP_MASTER_KEY_LEN 30
+#endif  // HAVE_SRTP
+
+namespace cricket {
+
+const std::string& CS_DEFAULT = CS_AES_CM_128_HMAC_SHA1_80;
+const std::string CS_AES_CM_128_HMAC_SHA1_80 = "AES_CM_128_HMAC_SHA1_80";
+const std::string CS_AES_CM_128_HMAC_SHA1_32 = "AES_CM_128_HMAC_SHA1_32";
+const int SRTP_MASTER_KEY_BASE64_LEN = SRTP_MASTER_KEY_LEN * 4 / 3;
+
+SrtpFilter::SrtpFilter() : state_(ST_INIT) {
+}
+
+SrtpFilter::~SrtpFilter() {
+}
+
+bool SrtpFilter::IsActive() const {
+  return (state_ == ST_ACTIVE);
+}
+
+bool SrtpFilter::SetOffer(const std::vector<CryptoParams>& offer_params,
+                          ContentSource source) {
+  bool ret = false;
+  if (state_ == ST_INIT) {
+    ret = StoreParams(offer_params, source);
+  } else {
+    LOG(LS_ERROR) << "Invalid state for SRTP offer";
+  }
+  return ret;
+}
+
+bool SrtpFilter::SetAnswer(const std::vector<CryptoParams>& answer_params,
+                           ContentSource source) {
+  bool ret = false;
+  if ((state_ == ST_SENTOFFER && source == CS_REMOTE) ||
+      (state_ == ST_RECEIVEDOFFER && source == CS_LOCAL)) {
+    // If the answer requests crypto, finalize the parameters and apply them.
+    // Otherwise, complete the negotiation of a unencrypted session.
+    if (!answer_params.empty()) {
+      CryptoParams selected_params;
+      ret = NegotiateParams(answer_params, &selected_params);
+      if (ret) {
+        if (state_ == ST_SENTOFFER) {
+          ret = ApplyParams(selected_params, answer_params[0]);
+        } else {  // ST_RECEIVEDOFFER
+          ret = ApplyParams(answer_params[0], selected_params);
+        }
+      }
+    } else {
+      ret = ResetParams();
+    }
+  } else {
+    LOG(LS_ERROR) << "Invalid state for SRTP answer";
+  }
+  return ret;
+}
+
+bool SrtpFilter::ProtectRtp(void* p, int in_len, int max_len, int* out_len) {
+  if (!IsActive()) {
+    LOG(LS_WARNING) << "Failed to ProtectRtp: SRTP not active";
+    return false;
+  }
+  return send_session_.ProtectRtp(p, in_len, max_len, out_len);
+}
+
+bool SrtpFilter::ProtectRtcp(void* p, int in_len, int max_len, int* out_len) {
+  if (!IsActive()) {
+    LOG(LS_WARNING) << "Failed to ProtectRtcp: SRTP not active";
+    return false;
+  }
+  return send_session_.ProtectRtcp(p, in_len, max_len, out_len);
+}
+
+bool SrtpFilter::UnprotectRtp(void* p, int in_len, int* out_len) {
+  if (!IsActive()) {
+    LOG(LS_WARNING) << "Failed to UnprotectRtp: SRTP not active";
+    return false;
+  }
+  return recv_session_.UnprotectRtp(p, in_len, out_len);
+}
+
+bool SrtpFilter::UnprotectRtcp(void* p, int in_len, int* out_len) {
+  if (!IsActive()) {
+    LOG(LS_WARNING) << "Failed to UnprotectRtcp: SRTP not active";
+    return false;
+  }
+  return recv_session_.UnprotectRtcp(p, in_len, out_len);
+}
+
+
+bool SrtpFilter::StoreParams(const std::vector<CryptoParams>& params,
+                             ContentSource source) {
+  offer_params_ = params;
+  state_ = (source == CS_LOCAL) ? ST_SENTOFFER : ST_RECEIVEDOFFER;
+  return true;
+}
+
+bool SrtpFilter::NegotiateParams(const std::vector<CryptoParams>& answer_params,
+                                 CryptoParams* selected_params) {
+  // We're processing an accept. We should have exactly one set of params,
+  // unless the offer didn't mention crypto, in which case we shouldn't be here.
+  bool ret = (answer_params.size() == 1U && !offer_params_.empty());
+  if (ret) {
+    // We should find a match between the answer params and the offered params.
+    std::vector<CryptoParams>::const_iterator it;
+    for (it = offer_params_.begin(); it != offer_params_.end(); ++it) {
+      if (answer_params[0].Matches(*it)) {
+        break;
+      }
+    }
+
+    if (it != offer_params_.end()) {
+      *selected_params = *it;
+    } else {
+      ret = false;
+    }
+  }
+
+  if (!ret) {
+    LOG(LS_WARNING) << "Invalid parameters in SRTP answer";
+  }
+  return ret;
+}
+
+bool SrtpFilter::ApplyParams(const CryptoParams& send_params,
+                             const CryptoParams& recv_params) {
+  // TODO: Zero these buffers after use.
+  bool ret;
+  uint8 send_key[SRTP_MASTER_KEY_LEN], recv_key[SRTP_MASTER_KEY_LEN];
+  ret = (ParseKeyParams(send_params.key_params, send_key, sizeof(send_key)) &&
+         ParseKeyParams(recv_params.key_params, recv_key, sizeof(recv_key)));
+  if (ret) {
+    ret = (send_session_.SetSend(send_params.cipher_suite,
+                                 send_key, sizeof(send_key)) &&
+           recv_session_.SetRecv(recv_params.cipher_suite,
+                                 recv_key, sizeof(recv_key)));
+  }
+  if (ret) {
+    offer_params_.clear();
+    state_ = ST_ACTIVE;
+    LOG(LS_INFO) << "SRTP activated with negotiated parameters:"
+                 << " send cipher_suite " << send_params.cipher_suite
+                 << " recv cipher_suite " << recv_params.cipher_suite;
+  } else {
+    LOG(LS_WARNING) << "Failed to apply negotiated SRTP parameters";
+  }
+  return ret;
+}
+
+bool SrtpFilter::ResetParams() {
+  offer_params_.clear();
+  state_ = ST_INIT;
+  LOG(LS_INFO) << "SRTP reset to init state";
+  return true;
+}
+
+bool SrtpFilter::ParseKeyParams(const std::string& key_params,
+                                uint8* key, int len) {
+  // example key_params: "inline:YUJDZGVmZ2hpSktMbW9QUXJzVHVWd3l6MTIzNDU2"
+
+  // Fail if key-method is wrong.
+  if (key_params.find("inline:") != 0) {
+    return false;
+  }
+
+  // Fail if base64 decode fails, or the key is the wrong size.
+  std::string key_b64(key_params.substr(7)), key_str;
+  if (!talk_base::Base64::Decode(key_b64, talk_base::Base64::DO_STRICT,
+                                 &key_str, NULL) ||
+      static_cast<int>(key_str.size()) != len) {
+    return false;
+  }
+
+  memcpy(key, key_str.c_str(), len);
+  return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// SrtpSession
+
+#ifdef HAVE_SRTP
+
+bool SrtpSession::inited_ = false;
+std::list<SrtpSession*> SrtpSession::sessions_;
+
+SrtpSession::SrtpSession()
+    : session_(NULL), rtp_auth_tag_len_(0), rtcp_auth_tag_len_(0) {
+  sessions_.push_back(this);
+}
+
+SrtpSession::~SrtpSession() {
+  sessions_.erase(std::find(sessions_.begin(), sessions_.end(), this));
+  if (session_) {
+    srtp_dealloc(session_);
+  }
+}
+
+bool SrtpSession::SetSend(const std::string& cs, const uint8* key, int len) {
+  return SetKey(ssrc_any_outbound, cs, key, len);
+}
+
+bool SrtpSession::SetRecv(const std::string& cs, const uint8* key, int len) {
+  return SetKey(ssrc_any_inbound, cs, key, len);
+}
+
+bool SrtpSession::ProtectRtp(void* p, int in_len, int max_len, int* out_len) {
+  if (!session_) {
+    LOG(LS_WARNING) << "Failed to protect SRTP packet: no SRTP Session";
+    return false;
+  }
+
+  int need_len = in_len + rtp_auth_tag_len_;  // NOLINT
+  if (max_len < need_len) {
+    LOG(LS_WARNING) << "Failed to protect SRTP packet: The buffer length "
+                    << max_len << " is less than the needed " << need_len;
+    return false;
+  }
+
+  *out_len = in_len;
+  int err = srtp_protect(session_, p, out_len);
+  if (err != err_status_ok) {
+    LOG(LS_WARNING) << "Failed to protect SRTP packet, err=" << err;
+    return false;
+  }
+  return true;
+}
+
+bool SrtpSession::ProtectRtcp(void* p, int in_len, int max_len, int* out_len) {
+  if (!session_) {
+    LOG(LS_WARNING) << "Failed to protect SRTCP packet: no SRTP Session";
+    return false;
+  }
+
+  int need_len = in_len + sizeof(uint32) + rtcp_auth_tag_len_;  // NOLINT
+  if (max_len < need_len) {
+    LOG(LS_WARNING) << "Failed to protect SRTCP packet: The buffer length "
+                    << max_len << " is less than the needed " << need_len;
+    return false;
+  }
+
+  *out_len = in_len;
+  int err = srtp_protect_rtcp(session_, p, out_len);
+  if (err != err_status_ok) {
+    LOG(LS_WARNING) << "Failed to protect SRTCP packet, err=" << err;
+    return false;
+  }
+  return true;
+}
+
+bool SrtpSession::UnprotectRtp(void* p, int in_len, int* out_len) {
+  if (!session_) {
+    LOG(LS_WARNING) << "Failed to unprotect SRTP packet: no SRTP Session";
+    return false;
+  }
+
+  *out_len = in_len;
+  int err = srtp_unprotect(session_, p, out_len);
+  if (err != err_status_ok) {
+    LOG(LS_WARNING) << "Failed to unprotect SRTP packet, err=" << err;
+    return false;
+  }
+  return true;
+}
+
+bool SrtpSession::UnprotectRtcp(void* p, int in_len, int* out_len) {
+  if (!session_) {
+    LOG(LS_WARNING) << "Failed to unprotect SRTCP packet: no SRTP Session";
+    return false;
+  }
+
+  *out_len = in_len;
+  int err = srtp_unprotect_rtcp(session_, p, out_len);
+  if (err != err_status_ok) {
+    LOG(LS_WARNING) << "Failed to unprotect SRTCP packet, err=" << err;
+    return false;
+  }
+  return true;
+}
+
+bool SrtpSession::SetKey(int type, const std::string& cs,
+                         const uint8* key, int len) {
+  if (session_) {
+    LOG(LS_ERROR) << "Failed to create SRTP session: "
+                  << "SRTP session already created";
+    return false;
+  }
+
+  if (!Init()) {
+    return false;
+  }
+
+  srtp_policy_t policy;
+  memset(&policy, 0, sizeof(policy));
+
+  if (cs == CS_AES_CM_128_HMAC_SHA1_80) {
+    crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtp);
+    crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtcp);
+  } else if (cs == CS_AES_CM_128_HMAC_SHA1_32) {
+    crypto_policy_set_aes_cm_128_hmac_sha1_32(&policy.rtp);   // rtp is 32,
+    crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtcp);  // rtcp still 80
+  } else {
+    LOG(LS_WARNING) << "Failed to create SRTP session: unsupported"
+                    << " cipher_suite " << cs.c_str();
+    return false;
+  }
+
+  if (!key || len != SRTP_MASTER_KEY_LEN) {
+    LOG(LS_WARNING) << "Failed to create SRTP session: invalid key";
+    return false;
+  }
+
+  policy.ssrc.type = static_cast<ssrc_type_t>(type);
+  policy.ssrc.value = 0;
+  policy.key = const_cast<uint8*>(key);
+  // TODO parse window size from WSH session-param
+  policy.window_size = 1024;
+  policy.allow_repeat_tx = 1;
+  policy.next = NULL;
+
+  int err = srtp_create(&session_, &policy);
+  if (err != err_status_ok) {
+    LOG(LS_ERROR) << "Failed to create SRTP session, err=" << err;
+    return false;
+  }
+
+  rtp_auth_tag_len_ = policy.rtp.auth_tag_len;
+  rtcp_auth_tag_len_ = policy.rtcp.auth_tag_len;
+  return true;
+}
+
+bool SrtpSession::Init() {
+  if (!inited_) {
+    int err;
+#ifdef DEBUG_SRTP
+    debug_on(mod_srtp);
+#endif
+    err = srtp_init();
+    if (err != err_status_ok) {
+      LOG(LS_ERROR) << "Failed to init SRTP, err=" << err;
+      return false;
+    }
+
+    err = srtp_install_event_handler(&SrtpSession::HandleEventThunk);
+    if (err != err_status_ok) {
+      LOG(LS_ERROR) << "Failed to install SRTP event handler, err=" << err;
+      return false;
+    }
+
+    inited_ = true;
+  }
+
+  return true;
+}
+
+void SrtpSession::HandleEvent(const srtp_event_data_t* ev) {
+  switch (ev->event) {
+    case event_ssrc_collision:
+      LOG(LS_INFO) << "SRTP event: SSRC collision";
+      break;
+    case event_key_soft_limit:
+      LOG(LS_INFO) << "SRTP event: reached soft key usage limit";
+      break;
+    case event_key_hard_limit:
+      LOG(LS_INFO) << "SRTP event: reached hard key usage limit";
+      break;
+    case event_packet_index_limit:
+      LOG(LS_INFO) << "SRTP event: reached hard packet limit (2^48 packets)";
+      break;
+    default:
+      LOG(LS_INFO) << "SRTP event: unknown " << ev->event;
+      break;
+  }
+}
+
+void SrtpSession::HandleEventThunk(srtp_event_data_t* ev) {
+  for (std::list<SrtpSession*>::iterator it = sessions_.begin();
+       it != sessions_.end(); ++it) {
+    if ((*it)->session_ == ev->session) {
+      (*it)->HandleEvent(ev);
+      break;
+    }
+  }
+}
+
+#else   // !HAVE_SRTP
+
+namespace {
+bool SrtpNotAvailable(const char *func) {
+  LOG(LS_ERROR) << func << ": SRTP is not available on your system.";
+  return false;
+}
+}  // anonymous namespace
+
+SrtpSession::SrtpSession() {
+  LOG(WARNING) << "SRTP implementation is missing.";
+}
+
+SrtpSession::~SrtpSession() {
+}
+
+bool SrtpSession::SetSend(const std::string& cs, const uint8* key, int len) {
+  return SrtpNotAvailable(__FUNCTION__);
+}
+
+bool SrtpSession::SetRecv(const std::string& cs, const uint8* key, int len) {
+  return SrtpNotAvailable(__FUNCTION__);
+}
+
+bool SrtpSession::ProtectRtp(void* data, int in_len, int max_len,
+                             int* out_len) {
+  return SrtpNotAvailable(__FUNCTION__);
+}
+
+bool SrtpSession::ProtectRtcp(void* data, int in_len, int max_len,
+                              int* out_len) {
+  return SrtpNotAvailable(__FUNCTION__);
+}
+
+bool SrtpSession::UnprotectRtp(void* data, int in_len, int* out_len) {
+  return SrtpNotAvailable(__FUNCTION__);
+}
+
+bool SrtpSession::UnprotectRtcp(void* data, int in_len, int* out_len) {
+  return SrtpNotAvailable(__FUNCTION__);
+}
+
+#endif  // HAVE_SRTP
+}  // namespace cricket
diff --git a/talk/session/phone/srtpfilter.h b/talk/session/phone/srtpfilter.h
new file mode 100644
index 0000000..3401004
--- /dev/null
+++ b/talk/session/phone/srtpfilter.h
@@ -0,0 +1,147 @@
+/*
+ * libjingle
+ * Copyright 2009, 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_PHONE_SRTPFILTER_H_
+#define TALK_SESSION_PHONE_SRTPFILTER_H_
+
+#include <list>
+#include <string>
+#include <vector>
+#include "talk/base/basictypes.h"
+#include "talk/session/phone/cryptoparams.h"
+#include "talk/p2p/base/sessiondescription.h"
+
+// Forward declaration to avoid pulling in libsrtp headers here
+struct srtp_event_data_t;
+struct srtp_ctx_t;
+typedef srtp_ctx_t* srtp_t;
+struct srtp_policy_t;
+
+namespace cricket {
+
+// Cipher suite to use for SRTP. Typically a 80-bit HMAC will be used, except
+// in applications (voice) where the additional bandwidth may be significant.
+// A 80-bit HMAC is always used for SRTCP.
+extern const std::string& CS_DEFAULT;
+// 128-bit AES with 80-bit SHA-1 HMAC.
+extern const std::string CS_AES_CM_128_HMAC_SHA1_80;
+// 128-bit AES with 32-bit SHA-1 HMAC.
+extern const std::string CS_AES_CM_128_HMAC_SHA1_32;
+// Key is 128 bits and salt is 112 bits == 30 bytes. B64 bloat => 40 bytes.
+extern const int SRTP_MASTER_KEY_BASE64_LEN;
+
+// Class that wraps a libSRTP session. Used internally by SrtpFilter, below.
+class SrtpSession {
+ public:
+  SrtpSession();
+  ~SrtpSession();
+
+  // Configures the session for sending data using the specified
+  // cipher-suite and key. Receiving must be done by a separate session.
+  bool SetSend(const std::string& cs, const uint8* key, int len);
+  // Configures the session for receiving data using the specified
+  // cipher-suite and key. Sending must be done by a separate session.
+  bool SetRecv(const std::string& cs, const uint8* key, int len);
+
+  // Encrypts/signs an individual RTP/RTCP packet, in-place.
+  // If an HMAC is used, this will increase the packet size.
+  bool ProtectRtp(void* data, int in_len, int max_len, int* out_len);
+  bool ProtectRtcp(void* data, int in_len, int max_len, int* out_len);
+  // Decrypts/verifies an invidiual RTP/RTCP packet.
+  // If an HMAC is used, this will decrease the packet size.
+  bool UnprotectRtp(void* data, int in_len, int* out_len);
+  bool UnprotectRtcp(void* data, int in_len, int* out_len);
+
+ private:
+  bool SetKey(int type, const std::string& cs, const uint8* key, int len);
+  static bool Init();
+  void HandleEvent(const srtp_event_data_t* ev);
+  static void HandleEventThunk(srtp_event_data_t* ev);
+
+  srtp_t session_;
+  int rtp_auth_tag_len_;
+  int rtcp_auth_tag_len_;
+  static bool inited_;
+  static std::list<SrtpSession*> sessions_;
+};
+
+// Class to transform SRTP to/from RTP.
+// Initialize by calling SetSend with the local security params, then call
+// SetRecv once the remote security params are received. At that point
+// Protect/UnprotectRt(c)p can be called to encrypt/decrypt data.
+// TODO: Figure out concurrency policy for SrtpFilter.
+class SrtpFilter {
+ public:
+  SrtpFilter();
+  ~SrtpFilter();
+
+  // Whether the filter is active (i.e. crypto has been properly negotiated).
+  bool IsActive() const;
+
+  // Indicates which crypto algorithms and keys were contained in the offer.
+  // offer_params should contain a list of available parameters to use, or none,
+  // if crypto is not desired. This must be called before SetAnswer.
+  bool SetOffer(const std::vector<CryptoParams>& offer_params,
+                ContentSource source);
+  // Indicates which crypto algorithms and keys were contained in the answer.
+  // answer_params should contain the negotiated parameters, which may be none,
+  // if crypto was not desired or could not be negotiated (and not required).
+  // This must be called after SetOffer. If crypto negotiation completes
+  // successfully, this will advance the filter to the active state.
+  bool SetAnswer(const std::vector<CryptoParams>& answer_params,
+                 ContentSource source);
+
+  // Encrypts/signs an individual RTP/RTCP packet, in-place.
+  // If an HMAC is used, this will increase the packet size.
+  bool ProtectRtp(void* data, int in_len, int max_len, int* out_len);
+  bool ProtectRtcp(void* data, int in_len, int max_len, int* out_len);
+  // Decrypts/verifies an invidiual RTP/RTCP packet.
+  // If an HMAC is used, this will decrease the packet size.
+  bool UnprotectRtp(void* data, int in_len, int* out_len);
+  bool UnprotectRtcp(void* data, int in_len, int* out_len);
+
+ protected:
+  bool StoreParams(const std::vector<CryptoParams>& offer_params,
+                   ContentSource source);
+  bool NegotiateParams(const std::vector<CryptoParams>& answer_params,
+                       CryptoParams* selected_params);
+  bool ApplyParams(const CryptoParams& send_params,
+                   const CryptoParams& recv_params);
+  bool ResetParams();
+  static bool ParseKeyParams(const std::string& params, uint8* key, int len);
+
+ private:
+  enum State { ST_INIT, ST_SENTOFFER, ST_RECEIVEDOFFER, ST_ACTIVE };
+  State state_;
+  std::vector<CryptoParams> offer_params_;
+  SrtpSession send_session_;
+  SrtpSession recv_session_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_SRTPFILTER_H_
diff --git a/talk/session/phone/testdata/video.rtpdump b/talk/session/phone/testdata/video.rtpdump
new file mode 100644
index 0000000..7be863e
--- /dev/null
+++ b/talk/session/phone/testdata/video.rtpdump
Binary files differ
diff --git a/talk/session/phone/testdata/voice.rtpdump b/talk/session/phone/testdata/voice.rtpdump
new file mode 100644
index 0000000..8f0ec15
--- /dev/null
+++ b/talk/session/phone/testdata/voice.rtpdump
Binary files differ
diff --git a/talk/session/phone/v4llookup.cc b/talk/session/phone/v4llookup.cc
new file mode 100644
index 0000000..e8436e1
--- /dev/null
+++ b/talk/session/phone/v4llookup.cc
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2009, Google Inc.
+ * Author: lexnikitin@google.com (Alexey Nikitin)
+ *
+ * V4LLookup provides basic functionality to work with V2L2 devices in Linux
+ * The functionality is implemented as a class with virtual methods for
+ * the purpose of unit testing.
+ */
+#include "talk/session/phone/v4llookup.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/types.h>
+#include <linux/videodev2.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <cstring>
+
+#include "talk/base/logging.h"
+
+namespace cricket {
+
+V4LLookup *V4LLookup::v4l_lookup_ = new V4LLookup();
+
+bool V4LLookup::CheckIsV4L2Device(const std::string& device_path) {
+  // check device major/minor numbers are in the range for video devices.
+  struct stat s;
+
+  if (lstat(device_path.c_str(), &s) != 0 || !S_ISCHR(s.st_mode)) return false;
+
+  int video_fd = -1;
+  bool is_v4l2 = false;
+
+  // check major/minur device numbers are in range for video device
+  if (major(s.st_rdev) == 81) {
+    dev_t num = minor(s.st_rdev);
+    if (num <= 63 && num >= 0) {
+      video_fd = ::open(device_path.c_str(), O_RDONLY | O_NONBLOCK);
+      if ((video_fd >= 0) || (errno == EBUSY)) {
+        ::v4l2_capability video_caps;
+        memset(&video_caps, 0, sizeof(video_caps));
+
+        if ((errno == EBUSY) ||
+            (::ioctl(video_fd, VIDIOC_QUERYCAP, &video_caps) >= 0 &&
+            (video_caps.capabilities & V4L2_CAP_VIDEO_CAPTURE))) {
+          LOG(LS_INFO) << "Found V4L2 capture device " << device_path;
+
+          is_v4l2 = true;
+        } else {
+          LOG(LS_ERROR) << "VIDIOC_QUERYCAP failed for " << device_path;
+        }
+      } else {
+        LOG(LS_ERROR) << "Failed to open " << device_path;
+      }
+    }
+  }
+
+  if (video_fd >= 0)
+    ::close(video_fd);
+
+  return is_v4l2;
+}
+
+};  // namespace cricket
diff --git a/talk/session/phone/v4llookup.h b/talk/session/phone/v4llookup.h
new file mode 100644
index 0000000..1150172
--- /dev/null
+++ b/talk/session/phone/v4llookup.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2009, Google Inc.
+ * Author: lexnikitin@google.com (Alexey Nikitin)
+ *
+ * V4LLookup provides basic functionality to work with V2L2 devices in Linux
+ * The functionality is implemented as a class with virtual methods for
+ * the purpose of unit testing.
+ */
+#ifndef TALK_SESSION_PHONE_V4LLOOKUP_H_
+#define TALK_SESSION_PHONE_V4LLOOKUP_H_
+
+#include <string>
+
+#ifdef LINUX
+namespace cricket {
+class V4LLookup {
+  public:
+    virtual ~V4LLookup() {}
+
+    static bool IsV4L2Device(const std::string& device_path) {
+      return GetV4LLookup()->CheckIsV4L2Device(device_path);
+    }
+
+    static void SetV4LLookup(V4LLookup* v4l_lookup) {
+      v4l_lookup_ = v4l_lookup;
+    }
+
+    static V4LLookup* GetV4LLookup() {
+      return v4l_lookup_;
+    }
+
+  protected:
+    static V4LLookup* v4l_lookup_;
+    // Making virtual so it is easier to mock
+    virtual bool CheckIsV4L2Device(const std::string& device_path);
+
+};
+}  // namespace cricket
+#endif
+#endif  // TALK_SESSION_PHONE_V4LLOOKUP_H_
diff --git a/talk/session/phone/videocommon.h b/talk/session/phone/videocommon.h
new file mode 100644
index 0000000..4fb311a
--- /dev/null
+++ b/talk/session/phone/videocommon.h
@@ -0,0 +1,171 @@
+// 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.
+//
+// Common definition for video, including fourcc and VideoFormat
+
+#ifndef TALK_SESSION_PHONE_VIDEOCOMMON_H_
+#define TALK_SESSION_PHONE_VIDEOCOMMON_H_
+
+#include <string>
+
+#include "talk/base/basictypes.h"
+
+namespace cricket {
+
+//////////////////////////////////////////////////////////////////////////////
+// Definition of fourcc.
+//////////////////////////////////////////////////////////////////////////////
+// Convert four characters to a fourcc code.
+// Needs to be a macro otherwise the OS X compiler complains when the kFormat*
+// constants are used in a switch.
+#define FOURCC(a, b, c, d) (\
+    (static_cast<uint32>(a)) | (static_cast<uint32>(b) << 8) | \
+    (static_cast<uint32>(c) << 16) | (static_cast<uint32>(d) << 24))
+
+// Get the name, that is, string with four characters, of a fourcc code.
+inline std::string GetFourccName(uint32 fourcc) {
+  std::string name;
+  name.push_back(static_cast<char>(fourcc & 0xFF));
+  name.push_back(static_cast<char>((fourcc >> 8) & 0xFF));
+  name.push_back(static_cast<char>((fourcc >> 16) & 0xFF));
+  name.push_back(static_cast<char>((fourcc >> 24) & 0xFF));
+  return name;
+}
+
+// FourCC codes used in Google Talk.
+// Some good pages discussing FourCC codes:
+//   http://developer.apple.com/quicktime/icefloe/dispatch020.html
+//   http://www.fourcc.org/yuv.php
+enum FourCC {
+  // Canonical fourcc codes used in our code.
+  FOURCC_I420 = FOURCC('I', '4', '2', '0'),
+  FOURCC_YUY2 = FOURCC('Y', 'U', 'Y', '2'),
+  FOURCC_UYVY = FOURCC('U', 'Y', 'V', 'Y'),
+  FOURCC_M420 = FOURCC('M', '4', '2', '0'),
+  FOURCC_24BG = FOURCC('2', '4', 'B', 'G'),
+  FOURCC_ABGR = FOURCC('A', 'B', 'G', 'R'),
+  FOURCC_BGRA = FOURCC('B', 'G', 'R', 'A'),
+  FOURCC_ARGB = FOURCC('A', 'R', 'G', 'B'),
+  FOURCC_MJPG = FOURCC('M', 'J', 'P', 'G'),
+  FOURCC_RAW  = FOURCC('r', 'a', 'w', ' '),
+  FOURCC_NV21 = FOURCC('N', 'V', '2', '1'),
+  FOURCC_NV12 = FOURCC('N', 'V', '1', '2'),
+  // Next four are Bayer RGB formats. The four characters define the order of
+  // the colours in each 2x2 pixel grid, going left-to-right and top-to-bottom.
+  FOURCC_RGGB = FOURCC('R', 'G', 'G', 'B'),
+  FOURCC_BGGR = FOURCC('B', 'G', 'G', 'R'),
+  FOURCC_GRBG = FOURCC('G', 'R', 'B', 'G'),
+  FOURCC_GBRG = FOURCC('G', 'B', 'R', 'G'),
+
+  // Aliases for canonical fourcc codes, replaced with their canonical
+  // equivalents by CanonicalFourCC().
+  FOURCC_IYUV = FOURCC('I', 'Y', 'U', 'V'),  // Alias for I420
+  FOURCC_YU12 = FOURCC('Y', 'U', '1', '2'),  // Alias for I420
+  FOURCC_YUYV = FOURCC('Y', 'U', 'Y', 'V'),  // Alias for YUY2
+  FOURCC_YUVS = FOURCC('y', 'u', 'v', 's'),  // Alias for YUY2 on Mac
+  FOURCC_HDYC = FOURCC('H', 'D', 'Y', 'C'),  // Alias for UYVY
+  FOURCC_2VUY = FOURCC('2', 'v', 'u', 'y'),  // Alias for UYVY
+  FOURCC_JPEG = FOURCC('J', 'P', 'E', 'G'),  // Alias for MJPG
+  FOURCC_BA81 = FOURCC('B', 'A', '8', '1'),  // Alias for BGGR
+
+  // Match any fourcc.
+  FOURCC_ANY  = 0xFFFFFFFF,
+};
+
+// Converts fourcc aliases into canonical ones.
+uint32 CanonicalFourCC(uint32 fourcc);
+
+//////////////////////////////////////////////////////////////////////////////
+// Definition of VideoFormat.
+//////////////////////////////////////////////////////////////////////////////
+
+static const int64 kNumNanosecsPerSec = 1000000000;
+
+struct VideoFormat {
+  static const int64 kMinimumInterval = kNumNanosecsPerSec / 10000;  // 10k fps
+
+  VideoFormat() : width(0), height(0), interval(0), fourcc(0) {}
+
+  VideoFormat(int w, int h, int64 interval_ns, uint32 cc)
+      : width(w),
+        height(h),
+        interval(interval_ns),
+        fourcc(cc) {
+  }
+
+  VideoFormat(const VideoFormat& format)
+      : width(format.width),
+        height(format.height),
+        interval(format.interval),
+        fourcc(format.fourcc) {
+  }
+
+  static int64 FpsToInterval(int fps) {
+    return fps ? kNumNanosecsPerSec / fps : kMinimumInterval;
+  }
+
+  static int IntervalToFps(int64 interval) {
+    // Normalize the interval first.
+    interval = talk_base::_max(interval, kMinimumInterval);
+    return static_cast<int>(kNumNanosecsPerSec / interval);
+  }
+
+  bool operator==(const VideoFormat& format) const {
+    return width == format.width && height == format.height &&
+        interval == format.interval && fourcc == format.fourcc;
+  }
+
+  bool operator!=(const VideoFormat& format) const {
+    return !(*this == format);
+  }
+
+  bool operator<(const VideoFormat& format) const {
+    return (fourcc < format.fourcc) ||
+        (fourcc == format.fourcc && width < format.width) ||
+        (fourcc == format.fourcc && width == format.width &&
+            height < format.height) ||
+        (fourcc == format.fourcc && width == format.width &&
+            height == format.height && interval > format.interval);
+  }
+
+  int framerate() const { return IntervalToFps(interval); }
+
+  int    width;     // in number of pixels
+  int    height;    // in number of pixels
+  int64  interval;  // in nanoseconds
+  uint32 fourcc;    // color space. FOURCC_ANY means that any color space is OK.
+};
+
+// Result of video capturer start.
+enum CaptureResult {
+  CR_SUCCESS,    // The capturer starts successfully.
+  CR_PENDING,    // The capturer is pending to start the capture device.
+  CR_FAILURE,    // The capturer fails to start.
+  CR_NO_DEVICE,  // The capturer has no device and fails to start.
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_VIDEOCOMMON_H_
diff --git a/talk/session/phone/voicechannel.h b/talk/session/phone/voicechannel.h
new file mode 100644
index 0000000..95de637
--- /dev/null
+++ b/talk/session/phone/voicechannel.h
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+#ifndef _VOICECHANNEL_H_
+#define _VOICECHANNEL_H_
+
+#include "talk/session/phone/channel.h"
+
+#endif // _VOICECHANNEL_H_
diff --git a/talk/session/tunnel/pseudotcpchannel.cc b/talk/session/tunnel/pseudotcpchannel.cc
new file mode 100644
index 0000000..0448853
--- /dev/null
+++ b/talk/session/tunnel/pseudotcpchannel.cc
@@ -0,0 +1,571 @@
+/*
+ * 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 "talk/base/basictypes.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stringutils.h"
+#include "talk/p2p/base/transportchannel.h"
+#include "pseudotcpchannel.h"
+
+using namespace talk_base;
+
+namespace cricket {
+
+extern const talk_base::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) {
+  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);
+  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->name() << ")";
+  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));
+  }
+}
+
+//
+// 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) {
+  //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 SocketAddress& addr) {
+  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
+  talk_base::scoped_ptr<Socket> mtu_socket(
+      worker_thread_->socketserver()->CreateSocket(SOCK_DGRAM));
+  if (mtu_socket->Connect(addr) < 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_->name());
+
+  } 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_);
+  int sent = channel_->SendPacket(buffer, len);
+  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/talk/session/tunnel/pseudotcpchannel.h b/talk/session/tunnel/pseudotcpchannel.h
new file mode 100644
index 0000000..fbcb611
--- /dev/null
+++ b/talk/session/tunnel/pseudotcpchannel.h
@@ -0,0 +1,129 @@
+/*
+ * 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 __PSEUDOTCPCHANNEL_H__
+#define __PSEUDOTCPCHANNEL_H__
+
+#include "talk/base/criticalsection.h"
+#include "talk/base/messagequeue.h"
+#include "talk/base/stream.h"
+#include "talk/p2p/base/pseudotcp.h"
+#include "talk/p2p/base/session.h"
+
+namespace talk_base {
+class Thread;
+}
+
+namespace cricket {
+
+class TransportChannel;
+
+///////////////////////////////////////////////////////////////////////////////
+// ChannelStream
+// 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, it's 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
+  : public IPseudoTcpNotify,
+    public talk_base::MessageHandler,
+    public sigslot::has_slots<> {
+public:
+  // Signal thread methods
+  PseudoTcpChannel(talk_base::Thread* stream_thread,
+                   Session* session);
+
+  bool Connect(const std::string& content_name,
+               const std::string& channel_name);
+  talk_base::StreamInterface* GetStream();
+
+  sigslot::signal1<PseudoTcpChannel*> SignalChannelClosed;
+
+  void OnSessionTerminate(Session* session);
+
+private:
+  class InternalStream;
+  friend class InternalStream;
+
+  virtual ~PseudoTcpChannel();
+
+  // Stream thread methods
+  talk_base::StreamState GetState() const;
+  talk_base::StreamResult Read(void* buffer, size_t buffer_len,
+                               size_t* read, int* error);
+  talk_base::StreamResult Write(const void* data, size_t data_len,
+                                size_t* written, int* error);
+  void Close();
+
+  // Multi-thread methods
+  void OnMessage(talk_base::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);
+  void OnChannelConnectionChanged(TransportChannel* channel,
+                                  const talk_base::SocketAddress& addr);
+
+  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);
+
+  talk_base::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 talk_base::CriticalSection cs_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace cricket
+
+#endif // __PSEUDOTCPCHANNEL_H__
diff --git a/talk/session/tunnel/securetunnelsessionclient.cc b/talk/session/tunnel/securetunnelsessionclient.cc
new file mode 100644
index 0000000..84ec5e0
--- /dev/null
+++ b/talk/session/tunnel/securetunnelsessionclient.cc
@@ -0,0 +1,382 @@
+/*
+ * 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 "talk/session/tunnel/securetunnelsessionclient.h"
+#include "talk/base/basicdefs.h"
+#include "talk/base/basictypes.h"
+#include "talk/base/common.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/sslidentity.h"
+#include "talk/base/sslstreamadapter.h"
+#include "talk/p2p/base/transportchannel.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/session/tunnel/pseudotcpchannel.h"
+
+namespace cricket {
+
+// XML elements and namespaces for XMPP stanzas used in content exchanges.
+
+const std::string NS_SECURE_TUNNEL("http://www.google.com/talk/securetunnel");
+const buzz::QName QN_SECURE_TUNNEL_DESCRIPTION(NS_SECURE_TUNNEL,
+                                               "description");
+const buzz::QName QN_SECURE_TUNNEL_TYPE(NS_SECURE_TUNNEL, "type");
+const buzz::QName QN_SECURE_TUNNEL_CLIENT_CERT(NS_SECURE_TUNNEL,
+                                               "client-cert");
+const buzz::QName QN_SECURE_TUNNEL_SERVER_CERT(NS_SECURE_TUNNEL,
+                                               "server-cert");
+const std::string 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) {
+  }
+};
+
+// SecureTunnelSessionClient
+
+SecureTunnelSessionClient::SecureTunnelSessionClient(
+    const buzz::Jid& jid, SessionManager* manager)
+    : TunnelSessionClient(jid, manager, NS_SECURE_TUNNEL) {
+}
+
+void SecureTunnelSessionClient::SetIdentity(talk_base::SSLIdentity* identity) {
+  ASSERT(identity_.get() == NULL);
+  identity_.reset(identity);
+}
+
+bool SecureTunnelSessionClient::GenerateIdentity() {
+  ASSERT(identity_.get() == NULL);
+  identity_.reset(talk_base::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;
+}
+
+talk_base::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 talk_base::SSLCertificate* ParseCertificate(
+    const std::string& pem_cert) {
+  if (pem_cert.empty())
+    return NULL;
+  return talk_base::SSLCertificate::FromPEMString(pem_cert, NULL);
+}
+
+TunnelSession* SecureTunnelSessionClient::MakeTunnelSession(
+    Session* session, talk_base::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
+  talk_base::scoped_ptr<talk_base::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,
+                                             const 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, const 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,
+    talk_base::Thread* stream_thread, TunnelSessionRole role)
+    : TunnelSession(client, session, stream_thread),
+      role_(role) {
+}
+
+talk_base::StreamInterface* SecureTunnelSession::MakeSecureStream(
+    talk_base::StreamInterface* stream) {
+  talk_base::SSLStreamAdapter* ssl_stream =
+      talk_base::SSLStreamAdapter::Create(stream);
+  talk_base::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 talk_base::StreamReference(ssl_stream));
+  return ssl_stream_reference_->NewReference();
+}
+
+talk_base::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;
+  talk_base::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);
+  talk_base::SSLStreamAdapter* ssl_stream =
+      static_cast<talk_base::SSLStreamAdapter*>(
+          ssl_stream_reference_->GetStream());
+  ssl_stream->SetPeerCertificate(peer_cert);  // pass ownership of certificate.
+  // 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"));
+}
+
+}  // namespace cricket
diff --git a/talk/session/tunnel/securetunnelsessionclient.h b/talk/session/tunnel/securetunnelsessionclient.h
new file mode 100644
index 0000000..cb9a99c
--- /dev/null
+++ b/talk/session/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 "talk/base/sslidentity.h"
+#include "talk/base/sslstreamadapter.h"
+#include "talk/session/tunnel/tunnelsessionclient.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(talk_base::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.
+  talk_base::SSLIdentity& GetIdentity() const;
+
+  // Inherited methods
+  virtual void OnIncomingTunnel(const buzz::Jid& jid, Session *session);
+  virtual bool ParseContent(SignalingProtocol protocol,
+                            const buzz::XmlElement* elem,
+                            const 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, talk_base::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.
+  talk_base::scoped_ptr<talk_base::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,
+                      talk_base::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 talk_base::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.
+  talk_base::StreamInterface* MakeSecureStream(
+      talk_base::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.
+  talk_base::scoped_ptr<talk_base::StreamReference> ssl_stream_reference_;
+
+  DISALLOW_EVIL_CONSTRUCTORS(SecureTunnelSession);
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_TUNNEL_SECURETUNNELSESSIONCLIENT_H_
diff --git a/talk/session/tunnel/tunnelsessionclient.cc b/talk/session/tunnel/tunnelsessionclient.cc
new file mode 100644
index 0000000..13d5c10
--- /dev/null
+++ b/talk/session/tunnel/tunnelsessionclient.cc
@@ -0,0 +1,398 @@
+/*
+ * 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 "talk/base/basicdefs.h"
+#include "talk/base/basictypes.h"
+#include "talk/base/common.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringutils.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/transportchannel.h"
+#include "talk/xmllite/xmlelement.h"
+#include "pseudotcpchannel.h"
+#include "tunnelsessionclient.h"
+
+namespace cricket {
+
+const std::string NS_TUNNEL("http://www.google.com/talk/tunnel");
+const buzz::QName QN_TUNNEL_DESCRIPTION(NS_TUNNEL, "description");
+const buzz::QName QN_TUNNEL_TYPE(NS_TUNNEL, "type");
+const std::string CN_TUNNEL("tunnel");
+
+enum {
+  MSG_CLOCK = 1,
+  MSG_DESTROY,
+  MSG_TERMINATE,
+  MSG_EVENT,
+  MSG_CREATE_TUNNEL,
+};
+
+struct EventData : public talk_base::MessageData {
+  int event, error;
+  EventData(int ev, int err = 0) : event(ev), error(err) { }
+};
+
+struct CreateTunnelData : public talk_base::MessageData {
+  buzz::Jid jid;
+  std::string description;
+  talk_base::Thread* thread;
+  talk_base::StreamInterface* stream;
+};
+
+extern const talk_base::ConstantLabel SESSION_STATES[];
+
+const talk_base::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) { }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// 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, talk_base::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;
+    }
+  }
+}
+
+talk_base::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 = talk_base::Thread::Current();
+  session_manager_->signaling_thread()->Send(this, MSG_CREATE_TUNNEL, &data);
+  return data.stream;
+}
+
+talk_base::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(talk_base::Message* pmsg) {
+  if (pmsg->message_id == MSG_CREATE_TUNNEL) {
+    ASSERT(session_manager_->signaling_thread()->IsCurrent());
+    CreateTunnelData* data = static_cast<CreateTunnelData*>(pmsg->pdata);
+    Session* session = session_manager_->CreateSession(jid_.Str(), namespace_);
+    TunnelSession* tunnel = MakeTunnelSession(session, data->thread,
+                                              INITIATOR);
+    sessions_.push_back(tunnel);
+    SessionDescription* offer = CreateOffer(data->jid, data->description);
+    session->Initiate(data->jid.Str(), offer);
+    data->stream = tunnel->GetStream();
+  }
+}
+
+TunnelSession* TunnelSessionClientBase::MakeTunnelSession(
+    Session* session, talk_base::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,
+                                       const 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, const 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) {
+  return NewTunnelSessionDescription(
+      CN_TUNNEL, new TunnelContentDescription(description));
+}
+
+SessionDescription* TunnelSessionClient::CreateAnswer(
+    const SessionDescription* offer) {
+  std::string content_name;
+  const TunnelContentDescription* offer_tunnel = NULL;
+  if (!FindTunnelContent(offer, &content_name, &offer_tunnel))
+    return NULL;
+
+  return NewTunnelSessionDescription(
+      content_name, new TunnelContentDescription(offer_tunnel->description));
+}
+///////////////////////////////////////////////////////////////////////////////
+// TunnelSession
+///////////////////////////////////////////////////////////////////////////////
+
+//
+// Signalling thread methods
+//
+
+TunnelSession::TunnelSession(TunnelSessionClientBase* client, Session* session,
+                             talk_base::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);
+}
+
+talk_base::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("
+               << talk_base::nonnull(
+                    talk_base::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"));
+}
+
+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/talk/session/tunnel/tunnelsessionclient.h b/talk/session/tunnel/tunnelsessionclient.h
new file mode 100644
index 0000000..7259fa1
--- /dev/null
+++ b/talk/session/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 "talk/base/criticalsection.h"
+#include "talk/base/stream.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/pseudotcp.h"
+#include "talk/p2p/base/session.h"
+#include "talk/p2p/base/sessiondescription.h"
+#include "talk/p2p/base/sessionmanager.h"
+#include "talk/p2p/base/sessionclient.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmpp/constants.h"
+
+namespace cricket {
+
+class TunnelSession;
+class TunnelStream;
+
+enum TunnelSessionRole { INITIATOR, RESPONDER };
+
+///////////////////////////////////////////////////////////////////////////////
+// TunnelSessionClient
+///////////////////////////////////////////////////////////////////////////////
+
+// Base class is still abstract
+class TunnelSessionClientBase
+  : public SessionClient, public talk_base::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.
+  talk_base::StreamInterface* CreateTunnel(const buzz::Jid& to,
+                                           const std::string& description);
+
+  talk_base::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(talk_base::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,
+                                           talk_base::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,
+                            const 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,
+                talk_base::Thread* stream_thread);
+
+  virtual talk_base::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/talk/site_scons/site_tools/talk_noops.py b/talk/site_scons/site_tools/talk_noops.py
new file mode 100644
index 0000000..bb8f106
--- /dev/null
+++ b/talk/site_scons/site_tools/talk_noops.py
@@ -0,0 +1,20 @@
+# Copyright 2010 Google Inc.
+# All Rights Reserved.
+# Author: thaloun@google.com (Tim Haloun)
+
+"""Noop tool that defines builder functions for non-default platforms to
+   avoid errors when scanning sconsscripts."""
+
+import SCons.Builder
+
+
+def generate(env):
+  """SCons method."""
+  if not env.Bit('windows'):
+    builder = SCons.Builder.Builder(
+      action=''
+    )
+    env.Append(BUILDERS={'RES': builder, 'Grit': builder})
+
+def exists(env):
+  return 1
diff --git a/talk/site_scons/talk.py b/talk/site_scons/talk.py
new file mode 100644
index 0000000..09e4dd9
--- /dev/null
+++ b/talk/site_scons/talk.py
@@ -0,0 +1,561 @@
+# Copyright 2010 Google Inc.
+# All Rights Reserved.
+#
+# Author: Tim Haloun (thaloun@google.com)
+#         Daniel Petersson (dape@google.com)
+#
+import os
+
+# Keep a global dictionary of library target params for lookups in
+# ExtendComponent().
+_all_lib_targets = {}
+
+def _GenericLibrary(env, static, **kwargs):
+  """Extends ComponentLibrary to support multiplatform builds
+     of dynamic or static libraries.
+
+  Args:
+    env: The environment object.
+    kwargs: The keyword arguments.
+
+  Returns:
+    See swtoolkit ComponentLibrary
+  """
+  params = CombineDicts(kwargs, {'COMPONENT_STATIC': static})
+  return ExtendComponent(env, 'ComponentLibrary', **params)
+
+
+def Library(env, **kwargs):
+  """Extends ComponentLibrary to support multiplatform builds of static
+     libraries.
+
+  Args:
+    env: The current environment.
+    kwargs: The keyword arguments.
+
+  Returns:
+    See swtoolkit ComponentLibrary
+  """
+  return _GenericLibrary(env, True, **kwargs)
+
+
+def DynamicLibrary(env, **kwargs):
+  """Extends ComponentLibrary to support multiplatform builds
+     of dynmic libraries.
+
+  Args:
+    env: The environment object.
+    kwargs: The keyword arguments.
+
+  Returns:
+    See swtoolkit ComponentLibrary
+  """
+  return _GenericLibrary(env, False, **kwargs)
+
+
+def Object(env, **kwargs):
+  return ExtendComponent(env, 'ComponentObject', **kwargs)
+
+
+def Unittest(env, **kwargs):
+  """Extends ComponentTestProgram to support unittest built
+     for multiple platforms.
+
+  Args:
+    env: The current environment.
+    kwargs: The keyword arguments.
+
+  Returns:
+    See swtoolkit ComponentProgram.
+  """
+  kwargs['name'] = kwargs['name'] + '_unittest'
+
+  common_test_params = {
+    'posix_cppdefines': ['GUNIT_NO_GOOGLE3', 'GTEST_HAS_RTTI=0'],
+    'libs': ['unittest_main', 'gunit']
+  }
+  if not kwargs.has_key('explicit_libs'):
+    common_test_params['win_libs'] = [
+      'advapi32',
+      'crypt32',
+      'iphlpapi',
+      'secur32',
+      'shell32',
+      'shlwapi',
+      'user32',
+      'wininet',
+      'ws2_32'
+    ]
+    common_test_params['lin_libs'] = [
+      'crypto',
+      'pthread',
+      'ssl',
+    ]
+
+  params = CombineDicts(kwargs, common_test_params)
+  return ExtendComponent(env, 'ComponentTestProgram', **params)
+
+
+def App(env, **kwargs):
+  """Extends ComponentProgram to support executables with platform specific
+     options.
+
+  Args:
+    env: The current environment.
+    kwargs: The keyword arguments.
+
+  Returns:
+    See swtoolkit ComponentProgram.
+  """
+  if not kwargs.has_key('explicit_libs'):
+    common_app_params = {
+      'win_libs': [
+        'advapi32',
+        'crypt32',
+        'iphlpapi',
+        'secur32',
+        'shell32',
+        'shlwapi',
+        'user32',
+        'wininet',
+        'ws2_32'
+      ]}
+    params = CombineDicts(kwargs, common_app_params)
+  else:
+    params = kwargs
+  return ExtendComponent(env, 'ComponentProgram', **params)
+
+def WiX(env, **kwargs):
+  """ Extends the WiX builder
+  Args:
+    env: The current environment.
+    kwargs: The keyword arguments.
+
+  Returns:
+    The node produced by the environment's wix builder
+  """
+  return ExtendComponent(env, 'WiX', **kwargs)
+
+def Repository(env, at, path):
+  """Maps a directory external to $MAIN_DIR to the given path so that sources
+     compiled from it end up in the correct place under $OBJ_DIR.  NOT required
+     when only referring to header files.
+
+  Args:
+    env: The current environment object.
+    at: The 'mount point' within the current directory.
+    path: Path to the actual directory.
+  """
+  env.Dir(at).addRepository(env.Dir(path))
+
+
+def Components(*paths):
+  """Completes the directory paths with the correct file
+     names such that the directory/directory.scons name
+     convention can be used.
+
+  Args:
+    paths: The paths to complete. If it refers to an existing
+           file then it is ignored.
+
+  Returns:
+    The completed lif scons files that are needed to build talk.
+  """
+  files = []
+  for path in paths:
+    if os.path.isfile(path):
+      files.append(path)
+    else:
+      files.append(ExpandSconsPath(path))
+  return files
+
+
+def ExpandSconsPath(path):
+  """Expands a directory path into the path to the
+     scons file that our build uses.
+     Ex: magiflute/plugin/common => magicflute/plugin/common/common.scons
+
+  Args:
+    path: The directory path to expand.
+
+  Returns:
+    The expanded path.
+  """
+  return '%s/%s.scons' % (path, os.path.basename(path))
+
+
+def AddMediaLibs(env, **kwargs):
+  lmi_libdir = '$GOOGLE3/../googleclient/third_party/lmi/files/lib/'
+  if env.Bit('windows'):
+    if env.get('COVERAGE_ENABLED'):
+      lmi_libdir += 'win32/c_only'
+    else:
+      lmi_libdir += 'win32/Release'
+  elif env.Bit('mac'):
+    lmi_libdir += 'macos'
+  elif env.Bit('linux'):
+      lmi_libdir += 'linux/x86'
+
+
+  AddToDict(kwargs, 'libdirs', [
+    '$MAIN_DIR/third_party/gips/Libraries/',
+    lmi_libdir,
+  ])
+
+  gips_lib = ''
+  if env.Bit('windows'):
+    if env.Bit('debug'):
+      gips_lib = 'gipsvoiceenginelib_mtd'
+    else:
+      gips_lib = 'gipsvoiceenginelib_mt'
+  elif env.Bit('mac'):
+    gips_lib = 'VoiceEngine_mac_universal_gcc'
+  elif env.Bit('linux'):
+      gips_lib = 'VoiceEngine_Linux_gcc'
+
+
+  AddToDict(kwargs, 'libs', [
+    gips_lib,
+    'LmiAudioCommon',
+    'LmiClient',
+    'LmiCmcp',
+    'LmiDeviceManager',
+    'LmiH263ClientPlugIn',
+    'LmiH263CodecCommon',
+    'LmiH263Decoder',
+    'LmiH263Encoder',
+    'LmiH264ClientPlugIn',
+    'LmiH264CodecCommon',
+    'LmiH264Common',
+    'LmiH264Decoder',
+    'LmiH264Encoder',
+    'LmiIce',
+    'LmiMediaPayload',
+    'LmiOs',
+    'LmiPacketCache',
+    'LmiProtocolStack',
+    'LmiRateShaper',
+    'LmiRtp',
+    'LmiSecurity',
+    'LmiSignaling',
+    'LmiStun',
+    'LmiTransport',
+    'LmiUi',
+    'LmiUtils',
+    'LmiVideoCommon',
+    'LmiXml',
+  ])
+
+  if env.Bit('windows'):
+    AddToDict(kwargs, 'libs', [
+      'dsound',
+      'd3d9',
+      'gdi32',
+      'strmiids',
+    ])
+
+  if env.Bit('mac'):
+    AddToDict(kwargs, 'FRAMEWORKS', [
+      'AudioToolbox',
+      'AudioUnit',
+      'Cocoa',
+      'CoreAudio',
+      'CoreFoundation',
+      'IOKit',
+      'QTKit',
+      'QuickTime',
+      'QuartzCore',
+    ])
+  return kwargs
+
+
+def ReadVersion(filename):
+  """Executes the supplied file and pulls out a version definition from it. """
+  defs = {}
+  execfile(str(filename), defs)
+  if not defs.has_key('version'):
+    return '0.0.0.0'
+  version = defs['version']
+  parts = version.split(',')
+  build = os.environ.get('GOOGLE_VERSION_BUILDNUMBER')
+  if build:
+    parts[-1] = str(build)
+  return '.'.join(parts)
+
+
+#-------------------------------------------------------------------------------
+# Helper methods for translating talk.Foo() declarations in to manipulations of
+# environmuent construction variables, including parameter parsing and merging,
+#
+def GetEntry(dict, key):
+  """Get the value from a dictionary by key. If the key
+     isn't in the dictionary then None is returned. If it is in
+     the dictionaruy the value is fetched and then is it removed
+     from the dictionary.
+
+  Args:
+    key: The key to get the value for.
+    kwargs: The keyword argument dictionary.
+  Returns:
+    The value or None if the key is missing.
+  """
+  value = None
+  if dict.has_key(key):
+    value = dict[key]
+    dict.pop(key)
+
+  return value
+
+
+def MergeAndFilterByPlatform(env, params):
+  """Take a dictionary of arguments to lists of values, and, depending on
+     which platform we are targetting, merge the lists of associated keys.
+     Merge by combining value lists like so:
+       {win_foo = [a,b], lin_foo = [c,d], foo = [e], mac_bar = [f], bar = [g] }
+       becomes {foo = [a,b,e], bar = [g]} on windows, and
+       {foo = [e], bar = [f,g]} on mac
+
+  Args:
+    env: The hammer environment which knows which platforms are active
+    params: The keyword argument dictionary.
+  Returns:
+    A new dictionary with the filtered and combined entries of params
+  """
+  platforms = {
+    'linux': 'lin_',
+    'mac': 'mac_',
+    'posix': 'posix_',
+    'windows': 'win_',
+  }
+  active_prefixes = [
+    platforms[x] for x in iter(platforms) if env.Bit(x)
+  ]
+  inactive_prefixes = [
+    platforms[x] for x in iter(platforms) if not env.Bit(x)
+  ]
+
+  merged = {}
+  for arg, values in params.iteritems():
+    inactive_platform = False
+
+    key = arg
+
+    for prefix in active_prefixes:
+      if arg.startswith(prefix):
+        key = arg[len(prefix):]
+
+    for prefix in inactive_prefixes:
+      if arg.startswith(prefix):
+        inactive_platform = True
+
+    if inactive_platform:
+      continue
+
+    AddToDict(merged, key, values)
+
+  return merged
+
+# Linux can build both 32 and 64 bit on 64 bit host, but 32 bit host can
+# only build 32 bit.  For 32 bit debian installer a 32 bit host is required.
+# ChromeOS (linux) ebuild don't support 64 bit and requires 32 bit build only
+# for now.
+def Allow64BitCompile(env):
+  return (env.Bit('linux') and env.Bit('platform_arch_64bit')
+          )
+
+def MergeSettingsFromLibraryDependencies(env, params):
+  if params.has_key('libs'):
+    for lib in params['libs']:
+      if (_all_lib_targets.has_key(lib) and
+          _all_lib_targets[lib].has_key('dependent_target_settings')):
+        params = CombineDicts(
+            params,
+            MergeAndFilterByPlatform(
+                env,
+                _all_lib_targets[lib]['dependent_target_settings']))
+  return params
+
+def ExtendComponent(env, component, **kwargs):
+  """A wrapper around a scons builder function that preprocesses and post-
+     processes its inputs and outputs.  For example, it merges and filters
+     certain keyword arguments before appending them to the environments
+     construction variables.  It can build signed targets and 64bit copies
+     of targets as well.
+
+  Args:
+    env: The hammer environment with which to build the target
+    component: The environment's builder function, e.g. ComponentProgram
+    kwargs: keyword arguments that are either merged, translated, and passed on
+            to the call to component, or which control execution.
+            TODO(): Document the fields, such as cppdefines->CPPDEFINES,
+            prepend_includedirs, include_talk_media_libs, etc.
+  Returns:
+    The output node returned by the call to component, or a subsequent signed
+    dependant node.
+  """
+  env = env.Clone()
+
+  # prune parameters intended for other platforms, then merge
+  params = MergeAndFilterByPlatform(env, kwargs)
+
+  # get the 'target' field
+  name = GetEntry(params, 'name')
+
+  # save pristine params of lib targets for future reference
+  if 'ComponentLibrary' == component:
+    _all_lib_targets[name] = dict(params)
+
+  # add any dependent target settings from library dependencies
+  params = MergeSettingsFromLibraryDependencies(env, params)
+
+  # if this is a signed binary we need to make an unsigned version first
+  signed = env.Bit('windows') and GetEntry(params, 'signed')
+  if signed:
+    name = 'unsigned_' + name
+
+  # add default values
+  if GetEntry(params, 'include_talk_media_libs'):
+    params = AddMediaLibs(env, **params)
+
+  # potentially exit now
+  srcs = GetEntry(params, 'srcs')
+  if not srcs or not hasattr(env, component):
+    return None
+
+  # apply any explicit dependencies
+  dependencies = GetEntry(params, 'depends')
+  if dependencies is not None:
+    env.Depends(name, dependencies)
+
+  # put the contents of params into the environment
+  # some entries are renamed then appended, others renamed then prepended
+  appends = {
+    'cppdefines' : 'CPPDEFINES',
+    'libdirs' : 'LIBPATH',
+    'link_flags' : 'LINKFLAGS',
+    'libs' : 'LIBS',
+    'FRAMEWORKS' : 'FRAMEWORKS',
+  }
+  prepends = {}
+  if env.Bit('windows'):
+    # MSVC compile flags have precedence at the beginning ...
+    prepends['ccflags'] = 'CCFLAGS'
+  else:
+    # ... while GCC compile flags have precedence at the end
+    appends['ccflags'] = 'CCFLAGS'
+  if GetEntry(params, 'prepend_includedirs'):
+    prepends['includedirs'] = 'CPPPATH'
+  else:
+    appends['includedirs'] = 'CPPPATH'
+
+  for field, var in appends.items():
+    values = GetEntry(params, field)
+    if values is not None:
+      env.Append(**{var : values})
+  for field, var in prepends.items():
+    values = GetEntry(params, field)
+    if values is not None:
+      env.Prepend(**{var : values})
+
+  # workaround for pulse stripping link flag for unknown reason
+  if Allow64BitCompile(env):
+    env['SHLINKCOM'] = ('$SHLINK -o $TARGET -m32 $SHLINKFLAGS $SOURCES '
+                        '$_LIBDIRFLAGS $_LIBFLAGS')
+    env['LINKCOM'] = ('$LINK -o $TARGET -m32 $LINKFLAGS $SOURCES '
+                      '$_LIBDIRFLAGS $_LIBFLAGS')
+
+  # any other parameters are replaced without renaming
+  for field, value in params.items():
+    env.Replace(**{field : value})
+
+  # invoke the builder function
+  builder = getattr(env, component)
+
+  node = builder(name, srcs)
+
+  # make a parallel 64bit version if requested
+  if Allow64BitCompile(env) and GetEntry(params, 'also64bit'):
+    env_64bit = env.Clone()
+    env_64bit.FilterOut(CCFLAGS = ['-m32'], LINKFLAGS = ['-m32'])
+    env_64bit.Prepend(CCFLAGS = ['-m64', '-fPIC'], LINKFLAGS = ['-m64'])
+    name_64bit = name + '64'
+    env_64bit.Replace(OBJSUFFIX = '64' + env_64bit['OBJSUFFIX'])
+    env_64bit.Replace(SHOBJSUFFIX = '64' + env_64bit['SHOBJSUFFIX'])
+    if ('ComponentProgram' == component or
+        ('ComponentLibrary' == component and
+         env_64bit['COMPONENT_STATIC'] == False)):
+      # link 64 bit versions of libraries
+      libs = []
+      for lib in env_64bit['LIBS']:
+        if (_all_lib_targets.has_key(lib) and
+            _all_lib_targets[lib].has_key('also64bit')):
+          libs.append(lib + '64')
+        else:
+          libs.append(lib)
+      env_64bit.Replace(LIBS = libs)
+
+    env_64bit['SHLINKCOM'] = ('$SHLINK -o $TARGET -m64 $SHLINKFLAGS $SOURCES '
+                              '$_LIBDIRFLAGS $_LIBFLAGS')
+    env_64bit['LINKCOM'] = ('$LINK -o $TARGET -m64 $LINKFLAGS $SOURCES '
+                            '$_LIBDIRFLAGS $_LIBFLAGS')
+    builder = getattr(env_64bit, component)
+    nodes = [node, builder(name_64bit, srcs)]
+    return nodes
+
+  if signed:  # Note currently incompatible with 64Bit flag
+    # Get the name of the built binary, then get the name of the final signed
+    # version from it.  We need the output path since we don't know the file
+    # extension beforehand.
+    target = node[0].path.split('_', 1)[1]
+    signed_node = env.SignedBinary(
+      source = node,
+      target = '$STAGING_DIR/' + target,
+    )
+    env.Alias('signed_binaries', signed_node)
+    return signed_node
+
+  return node
+
+
+def AddToDict(dictionary, key, values, append=True):
+  """Merge the given key value(s) pair into a dictionary.  If it contains an
+     entry with that key already, then combine by appending or prepending the
+     values as directed.  Otherwise, assign a new keyvalue pair.
+  """
+  if values is None:
+    return
+
+  if not dictionary.has_key(key):
+    dictionary[key] = values
+    return
+
+  cur = dictionary[key]
+  # TODO: Make sure that there are no duplicates
+  # in the list. I can't use python set for this since
+  # the nodes that are returned by the SCONS builders
+  # are not hashable.
+  # dictionary[key] = list(set(cur).union(set(values)))
+  if append:
+    dictionary[key] = cur + values
+  else:
+    dictionary[key] = values + cur
+
+
+def CombineDicts(a, b):
+  """Unions two dictionaries by combining values of keys shared between them.
+  """
+  c = {}
+  for key in a:
+    if b.has_key(key):
+      c[key] = a[key] + b.pop(key)
+    else:
+      c[key] = a[key]
+
+  for key in b:
+    c[key] = b[key]
+
+  return c
+
+
+def RenameKey(d, old, new, append=True):
+  AddToDict(d, new, GetEntry(d, old), append)
diff --git a/talk/third_party/libudev/libudev.h b/talk/third_party/libudev/libudev.h
new file mode 100644
index 0000000..5bc42df
--- /dev/null
+++ b/talk/third_party/libudev/libudev.h
@@ -0,0 +1,175 @@
+/*
+ * libudev - interface to udev device information
+ *
+ * Copyright (C) 2008-2010 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ */
+
+#ifndef _LIBUDEV_H_
+#define _LIBUDEV_H_
+
+#include <stdarg.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * udev - library context
+ * 
+ * reads the udev config and system environment
+ * allows custom logging
+ */
+struct udev;
+struct udev *udev_ref(struct udev *udev);
+void udev_unref(struct udev *udev);
+struct udev *udev_new(void);
+void udev_set_log_fn(struct udev *udev,
+			    void (*log_fn)(struct udev *udev,
+					   int priority, const char *file, int line, const char *fn,
+					   const char *format, va_list args));
+int udev_get_log_priority(struct udev *udev);
+void udev_set_log_priority(struct udev *udev, int priority);
+const char *udev_get_sys_path(struct udev *udev);
+const char *udev_get_dev_path(struct udev *udev);
+void *udev_get_userdata(struct udev *udev);
+void udev_set_userdata(struct udev *udev, void *userdata);
+
+/*
+ * udev_list
+ *
+ * access to libudev generated lists
+ */
+struct udev_list_entry;
+struct udev_list_entry *udev_list_entry_get_next(struct udev_list_entry *list_entry);
+struct udev_list_entry *udev_list_entry_get_by_name(struct udev_list_entry *list_entry, const char *name);
+const char *udev_list_entry_get_name(struct udev_list_entry *list_entry);
+const char *udev_list_entry_get_value(struct udev_list_entry *list_entry);
+/**
+ * udev_list_entry_foreach:
+ * @list_entry: entry to store the current position
+ * @first_entry: first entry to start with
+ *
+ * Helper to iterate over all entries of a list.
+ */
+#define udev_list_entry_foreach(list_entry, first_entry) \
+	for (list_entry = first_entry; \
+	     list_entry != NULL; \
+	     list_entry = udev_list_entry_get_next(list_entry))
+
+/*
+ * udev_device
+ *
+ * access to sysfs/kernel devices
+ */
+struct udev_device;
+struct udev_device *udev_device_ref(struct udev_device *udev_device);
+void udev_device_unref(struct udev_device *udev_device);
+struct udev *udev_device_get_udev(struct udev_device *udev_device);
+struct udev_device *udev_device_new_from_syspath(struct udev *udev, const char *syspath);
+struct udev_device *udev_device_new_from_devnum(struct udev *udev, char type, dev_t devnum);
+struct udev_device *udev_device_new_from_subsystem_sysname(struct udev *udev, const char *subsystem, const char *sysname);
+struct udev_device *udev_device_new_from_environment(struct udev *udev);
+/* udev_device_get_parent_*() does not take a reference on the returned device, it is automatically unref'd with the parent */
+struct udev_device *udev_device_get_parent(struct udev_device *udev_device);
+struct udev_device *udev_device_get_parent_with_subsystem_devtype(struct udev_device *udev_device,
+								  const char *subsystem, const char *devtype);
+/* retrieve device properties */
+const char *udev_device_get_devpath(struct udev_device *udev_device);
+const char *udev_device_get_subsystem(struct udev_device *udev_device);
+const char *udev_device_get_devtype(struct udev_device *udev_device);
+const char *udev_device_get_syspath(struct udev_device *udev_device);
+const char *udev_device_get_sysname(struct udev_device *udev_device);
+const char *udev_device_get_sysnum(struct udev_device *udev_device);
+const char *udev_device_get_devnode(struct udev_device *udev_device);
+struct udev_list_entry *udev_device_get_devlinks_list_entry(struct udev_device *udev_device);
+struct udev_list_entry *udev_device_get_properties_list_entry(struct udev_device *udev_device);
+struct udev_list_entry *udev_device_get_tags_list_entry(struct udev_device *udev_device);
+const char *udev_device_get_property_value(struct udev_device *udev_device, const char *key);
+const char *udev_device_get_driver(struct udev_device *udev_device);
+dev_t udev_device_get_devnum(struct udev_device *udev_device);
+const char *udev_device_get_action(struct udev_device *udev_device);
+unsigned long long int udev_device_get_seqnum(struct udev_device *udev_device);
+const char *udev_device_get_sysattr_value(struct udev_device *udev_device, const char *sysattr);
+
+/*
+ * udev_monitor
+ *
+ * access to kernel uevents and udev events
+ */
+struct udev_monitor;
+struct udev_monitor *udev_monitor_ref(struct udev_monitor *udev_monitor);
+void udev_monitor_unref(struct udev_monitor *udev_monitor);
+struct udev *udev_monitor_get_udev(struct udev_monitor *udev_monitor);
+/* kernel and udev generated events over netlink */
+struct udev_monitor *udev_monitor_new_from_netlink(struct udev *udev, const char *name);
+/* custom socket (use netlink and filters instead) */
+struct udev_monitor *udev_monitor_new_from_socket(struct udev *udev, const char *socket_path);
+/* bind socket */
+int udev_monitor_enable_receiving(struct udev_monitor *udev_monitor);
+int udev_monitor_set_receive_buffer_size(struct udev_monitor *udev_monitor, int size);
+int udev_monitor_get_fd(struct udev_monitor *udev_monitor);
+struct udev_device *udev_monitor_receive_device(struct udev_monitor *udev_monitor);
+/* in-kernel socket filters to select messages that get delivered to a listener */
+int udev_monitor_filter_add_match_subsystem_devtype(struct udev_monitor *udev_monitor,
+						    const char *subsystem, const char *devtype);
+int udev_monitor_filter_add_match_tag(struct udev_monitor *udev_monitor, const char *tag);
+int udev_monitor_filter_update(struct udev_monitor *udev_monitor);
+int udev_monitor_filter_remove(struct udev_monitor *udev_monitor);
+
+/*
+ * udev_enumerate
+ *
+ * search sysfs for specific devices and provide a sorted list
+ */
+struct udev_enumerate;
+struct udev_enumerate *udev_enumerate_ref(struct udev_enumerate *udev_enumerate);
+void udev_enumerate_unref(struct udev_enumerate *udev_enumerate);
+struct udev *udev_enumerate_get_udev(struct udev_enumerate *udev_enumerate);
+struct udev_enumerate *udev_enumerate_new(struct udev *udev);
+/* device properties filter */
+int udev_enumerate_add_match_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem);
+int udev_enumerate_add_nomatch_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem);
+int udev_enumerate_add_match_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value);
+int udev_enumerate_add_nomatch_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value);
+int udev_enumerate_add_match_property(struct udev_enumerate *udev_enumerate, const char *property, const char *value);
+int udev_enumerate_add_match_sysname(struct udev_enumerate *udev_enumerate, const char *sysname);
+int udev_enumerate_add_match_tag(struct udev_enumerate *udev_enumerate, const char *tag);
+int udev_enumerate_add_syspath(struct udev_enumerate *udev_enumerate, const char *syspath);
+/* run enumeration with active filters */
+int udev_enumerate_scan_devices(struct udev_enumerate *udev_enumerate);
+int udev_enumerate_scan_subsystems(struct udev_enumerate *udev_enumerate);
+/* return device list */
+struct udev_list_entry *udev_enumerate_get_list_entry(struct udev_enumerate *udev_enumerate);
+
+/*
+ * udev_queue
+ *
+ * access to the currently running udev events
+ */
+struct udev_queue;
+struct udev_queue *udev_queue_ref(struct udev_queue *udev_queue);
+void udev_queue_unref(struct udev_queue *udev_queue);
+struct udev *udev_queue_get_udev(struct udev_queue *udev_queue);
+struct udev_queue *udev_queue_new(struct udev *udev);
+unsigned long long int udev_queue_get_kernel_seqnum(struct udev_queue *udev_queue);
+unsigned long long int udev_queue_get_udev_seqnum(struct udev_queue *udev_queue);
+int udev_queue_get_udev_is_active(struct udev_queue *udev_queue);
+int udev_queue_get_queue_is_empty(struct udev_queue *udev_queue);
+int udev_queue_get_seqnum_is_finished(struct udev_queue *udev_queue, unsigned long long int seqnum);
+int udev_queue_get_seqnum_sequence_is_finished(struct udev_queue *udev_queue,
+					       unsigned long long int start, unsigned long long int end);
+struct udev_list_entry *udev_queue_get_queued_list_entry(struct udev_queue *udev_queue);
+struct udev_list_entry *udev_queue_get_failed_list_entry(struct udev_queue *udev_queue);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif
diff --git a/talk/xmllite/qname.cc b/talk/xmllite/qname.cc
new file mode 100644
index 0000000..7486524
--- /dev/null
+++ b/talk/xmllite/qname.cc
@@ -0,0 +1,162 @@
+/*
+ * 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 <string>
+#include "talk/base/common.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlconstants.h"
+
+namespace buzz {
+
+static int QName_Hash(const std::string & ns, const char * local) {
+  int result = static_cast<int>(ns.size()) * 101;
+  while (*local) {
+    result *= 19;
+    result += *local;
+    local += 1;
+  }
+  return result;
+}
+
+static const int bits = 9;
+static QName::Data * get_qname_table() {
+  static QName::Data qname_table[1 << bits];
+  return qname_table;
+}
+
+static QName::Data *
+AllocateOrFind(const std::string & ns, const char * local) {
+  int index = QName_Hash(ns, local);
+  int increment = index >> (bits - 1) | 1;
+  QName::Data * qname_table = get_qname_table();
+  for (;;) {
+    index &= ((1 << bits) - 1);
+    if (!qname_table[index].Occupied()) {
+      return new QName::Data(ns, local);
+    }
+    if (qname_table[index].localPart_ == local &&
+        qname_table[index].namespace_ == ns) {
+      qname_table[index].AddRef();
+      return qname_table + index;
+    }
+    index += increment;
+  }
+}
+
+static QName::Data *
+Add(const std::string & ns, const char * local) {
+  int index = QName_Hash(ns, local);
+  int increment = index >> (bits - 1) | 1;
+  QName::Data * qname_table = get_qname_table();
+  for (;;) {
+    index &= ((1 << bits) - 1);
+    if (!qname_table[index].Occupied()) {
+      qname_table[index].namespace_ = ns;
+      qname_table[index].localPart_ = local;
+      qname_table[index].AddRef(); // AddRef twice so it's never deleted
+      qname_table[index].AddRef();
+      return qname_table + index;
+    }
+    if (qname_table[index].localPart_ == local &&
+        qname_table[index].namespace_ == ns) {
+      qname_table[index].AddRef();
+      return qname_table + index;
+    }
+    index += increment;
+  }
+}
+
+QName::~QName() {
+  data_->Release();
+}
+
+QName::QName() : data_(QN_EMPTY.data_) {
+  data_->AddRef();
+}
+
+QName::QName(bool add, const std::string & ns, const char * local) :
+  data_(add ? Add(ns, local) : AllocateOrFind(ns, local)) {}
+
+QName::QName(bool add, const std::string & ns, const std::string & local) :
+  data_(add ? Add(ns, local.c_str()) : AllocateOrFind(ns, local.c_str())) {}
+
+QName::QName(const std::string & ns, const char * local) :
+  data_(AllocateOrFind(ns, local)) {}
+
+static std::string
+QName_LocalPart(const std::string & name) {
+  size_t i = name.rfind(':');
+  if (i == std::string::npos)
+    return name;
+  return name.substr(i + 1);
+}
+
+static std::string
+QName_Namespace(const std::string & name) {
+  size_t i = name.rfind(':');
+  if (i == std::string::npos)
+    return STR_EMPTY;
+  return name.substr(0, i);
+}
+
+QName::QName(const std::string & mergedOrLocal) :
+  data_(AllocateOrFind(QName_Namespace(mergedOrLocal),
+                 QName_LocalPart(mergedOrLocal).c_str())) {}
+
+std::string
+QName::Merged() const {
+  if (data_->namespace_ == STR_EMPTY)
+    return data_->localPart_;
+
+  std::string result(data_->namespace_);
+  result.reserve(result.length() + 1 + data_->localPart_.length());
+  result += ':';
+  result += data_->localPart_;
+  return result;
+}
+
+bool
+QName::operator==(const QName & other) const {
+  return other.data_ == data_ ||
+      (data_->localPart_ == other.data_->localPart_ &&
+       data_->namespace_ == other.data_->namespace_);
+}
+
+int
+QName::Compare(const QName & other) const {
+  if (data_ == other.data_)
+    return 0;
+
+  int result = data_->localPart_.compare(other.data_->localPart_);
+  if (result)
+    return result;
+
+  return data_->namespace_.compare(other.data_->namespace_);
+}
+
+}
diff --git a/talk/xmllite/qname.h b/talk/xmllite/qname.h
new file mode 100644
index 0000000..3e64726
--- /dev/null
+++ b/talk/xmllite/qname.h
@@ -0,0 +1,87 @@
+/*
+ * 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 _qname_h_
+#define _qname_h_
+
+#include <string>
+
+namespace buzz {
+
+
+class QName
+{
+public:
+  explicit QName();
+  QName(const QName & qname) : data_(qname.data_) { data_->AddRef(); }
+  explicit QName(bool add, const std::string & ns, const char * local);
+  explicit QName(bool add, const std::string & ns, const std::string & local);
+  explicit QName(const std::string & ns, const char * local);
+  explicit QName(const std::string & mergedOrLocal);
+  QName & operator=(const QName & qn) {
+    qn.data_->AddRef();
+    data_->Release();
+    data_ = qn.data_;
+    return *this;
+  }
+  ~QName();
+  
+  const std::string & Namespace() const { return data_->namespace_; }
+  const std::string & LocalPart() const { return data_->localPart_; }
+  std::string Merged() const;
+  int Compare(const QName & other) const;
+  bool operator==(const QName & other) const;
+  bool operator!=(const QName & other) const { return !operator==(other); }
+  bool operator<(const QName & other) const { return Compare(other) < 0; }
+  
+  class Data {
+  public:
+    Data(const std::string & ns, const std::string & local) :
+      namespace_(ns),
+      localPart_(local),
+      refcount_(1) {}
+
+    Data() : refcount_(0) {}
+      
+    std::string namespace_;
+    std::string localPart_;
+    void AddRef() { refcount_++; }
+    void Release() { if (!--refcount_) { delete this; } }
+    bool Occupied() { return !!refcount_; }
+
+  private:
+    int refcount_;
+  };
+
+private:
+  Data * data_;
+};
+
+
+}
+
+#endif
diff --git a/talk/xmllite/xmlbuilder.cc b/talk/xmllite/xmlbuilder.cc
new file mode 100644
index 0000000..b02dfe0
--- /dev/null
+++ b/talk/xmllite/xmlbuilder.cc
@@ -0,0 +1,152 @@
+/*
+ * 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 <vector>
+#include <set>
+#include "talk/base/common.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmllite/xmlbuilder.h"
+#ifdef EXPAT_RELATIVE_PATH
+#include "lib/expat.h"
+#else
+#include "third_party/expat/v2_0_1/Source/lib/expat.h"
+#endif  // EXPAT_RELATIVE_PATH
+
+namespace buzz {
+
+XmlBuilder::XmlBuilder() :
+  pelCurrent_(NULL),
+  pelRoot_(NULL),
+  pvParents_(new std::vector<XmlElement *>()) {
+}
+
+void
+XmlBuilder::Reset() {
+  pelRoot_.reset();
+  pelCurrent_ = NULL;
+  pvParents_->clear();
+}
+
+XmlElement *
+XmlBuilder::BuildElement(XmlParseContext * pctx,
+                              const char * name, const char ** atts) {
+  QName tagName(pctx->ResolveQName(name, false));
+  if (tagName == QN_EMPTY)
+    return NULL;
+
+  XmlElement * pelNew = new XmlElement(tagName);
+
+  if (!*atts)
+    return pelNew;
+
+  std::set<QName> seenNonlocalAtts;
+
+  while (*atts) {
+    QName attName(pctx->ResolveQName(*atts, true));
+    if (attName == QN_EMPTY) {
+      delete pelNew;
+      return NULL;
+    }
+
+    // verify that namespaced names are unique
+    if (!attName.Namespace().empty()) {
+      if (seenNonlocalAtts.count(attName)) {
+        delete pelNew;
+        return NULL;
+      }
+      seenNonlocalAtts.insert(attName);
+    }
+
+    pelNew->AddAttr(attName, std::string(*(atts + 1)));
+    atts += 2;
+  }
+
+  return pelNew;
+}
+
+void
+XmlBuilder::StartElement(XmlParseContext * pctx,
+                              const char * name, const char ** atts) {
+  XmlElement * pelNew = BuildElement(pctx, name, atts);
+  if (pelNew == NULL) {
+    pctx->RaiseError(XML_ERROR_SYNTAX);
+    return;
+  }
+
+  if (!pelCurrent_) {
+    pelCurrent_ = pelNew;
+    pelRoot_.reset(pelNew);
+    pvParents_->push_back(NULL);
+  } else {
+    pelCurrent_->AddElement(pelNew);
+    pvParents_->push_back(pelCurrent_);
+    pelCurrent_ = pelNew;
+  }
+}
+
+void
+XmlBuilder::EndElement(XmlParseContext * pctx, const char * name) {
+  UNUSED(pctx);
+  UNUSED(name);
+  pelCurrent_ = pvParents_->back();
+  pvParents_->pop_back();
+}
+
+void
+XmlBuilder::CharacterData(XmlParseContext * pctx,
+                               const char * text, int len) {
+  UNUSED(pctx);
+  if (pelCurrent_) {
+    pelCurrent_->AddParsedText(text, len);
+  }
+}
+
+void
+XmlBuilder::Error(XmlParseContext * pctx, XML_Error err) {
+  UNUSED(pctx);
+  UNUSED(err);
+  pelRoot_.reset(NULL);
+  pelCurrent_ = NULL;
+  pvParents_->clear();
+}
+
+XmlElement *
+XmlBuilder::CreateElement() {
+  return pelRoot_.release();
+}
+
+XmlElement *
+XmlBuilder::BuiltElement() {
+  return pelRoot_.get();
+}
+
+XmlBuilder::~XmlBuilder() {
+}
+
+
+
+}
diff --git a/talk/xmllite/xmlbuilder.h b/talk/xmllite/xmlbuilder.h
new file mode 100644
index 0000000..d375985
--- /dev/null
+++ b/talk/xmllite/xmlbuilder.h
@@ -0,0 +1,78 @@
+/*
+ * 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 _xmlbuilder_h_
+#define _xmlbuilder_h_
+
+#include <string>
+#include <vector>
+#include "talk/base/scoped_ptr.h"
+#include "talk/xmllite/xmlparser.h"
+
+#ifdef EXPAT_RELATIVE_PATH
+#include "lib/expat.h"
+#else
+#include "third_party/expat/v2_0_1/Source/lib/expat.h"
+#endif  // EXPAT_RELATIVE_PATH
+
+namespace buzz {
+
+class XmlElement;
+class XmlParseContext;
+
+
+class XmlBuilder : public XmlParseHandler {
+public:
+  XmlBuilder();
+
+  static XmlElement * BuildElement(XmlParseContext * pctx,
+                                  const char * name, const char ** atts);
+  virtual void StartElement(XmlParseContext * pctx,
+                            const char * name, const char ** atts);
+  virtual void EndElement(XmlParseContext * pctx, const char * name);
+  virtual void CharacterData(XmlParseContext * pctx,
+                             const char * text, int len);
+  virtual void Error(XmlParseContext * pctx, XML_Error);
+  virtual ~XmlBuilder();
+
+  void Reset();
+
+  // Take ownership of the built element; second call returns NULL
+  XmlElement * CreateElement();
+
+  // Peek at the built element without taking ownership
+  XmlElement * BuiltElement();
+
+private:
+  XmlElement * pelCurrent_;
+  talk_base::scoped_ptr<XmlElement> pelRoot_;
+  talk_base::scoped_ptr<std::vector<XmlElement*> > pvParents_;
+};
+
+}
+
+#endif
diff --git a/talk/xmllite/xmlconstants.cc b/talk/xmllite/xmlconstants.cc
new file mode 100644
index 0000000..503f832
--- /dev/null
+++ b/talk/xmllite/xmlconstants.cc
@@ -0,0 +1,65 @@
+/*
+ * 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 "xmlconstants.h"
+
+using namespace buzz;
+
+const std::string & XmlConstants::str_empty() {
+  static const std::string str_empty_;
+  return str_empty_;
+}
+
+const std::string & XmlConstants::ns_xml() {
+  static const std::string ns_xml_("http://www.w3.org/XML/1998/namespace");
+  return ns_xml_;
+}
+
+const std::string & XmlConstants::ns_xmlns() {
+  static const std::string ns_xmlns_("http://www.w3.org/2000/xmlns/");
+  return ns_xmlns_;
+}
+
+const std::string & XmlConstants::str_xmlns() {
+  static const std::string str_xmlns_("xmlns");
+  return str_xmlns_;
+}
+
+const std::string & XmlConstants::str_xml() {
+  static const std::string str_xml_("xml");
+  return str_xml_;
+}
+
+const std::string & XmlConstants::str_version() {
+  static const std::string str_version_("version");
+  return str_version_;
+}
+
+const std::string & XmlConstants::str_encoding() {
+  static const std::string str_encoding_("encoding");
+  return str_encoding_;
+}
diff --git a/talk/xmllite/xmlconstants.h b/talk/xmllite/xmlconstants.h
new file mode 100644
index 0000000..8514d6f
--- /dev/null
+++ b/talk/xmllite/xmlconstants.h
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+
+// Because global constant initialization order is undefined
+// globals cannot depend on other objects to be instantiated.
+// This class creates string objects within static methods 
+// such that globals may refer to these constants by the
+// accessor function and they are guaranteed to be initialized.
+
+#ifndef TALK_XMLLITE_CONSTANTS_H_
+#define TALK_XMLLITE_CONSTANTS_H_
+
+#include <string>
+
+#define STR_EMPTY    XmlConstants::str_empty()
+#define NS_XML       XmlConstants::ns_xml()
+#define NS_XMLNS     XmlConstants::ns_xmlns()
+#define STR_XMLNS    XmlConstants::str_xmlns()
+#define STR_XML      XmlConstants::str_xml()
+#define STR_VERSION  XmlConstants::str_version()
+#define STR_ENCODING XmlConstants::str_encoding()
+namespace buzz {
+	
+class XmlConstants {
+ public:
+  static const std::string & str_empty();
+  static const std::string & ns_xml();
+  static const std::string & ns_xmlns();
+  static const std::string & str_xmlns();
+  static const std::string & str_xml();
+  static const std::string & str_version();
+  static const std::string & str_encoding();
+};
+
+}
+
+#endif  // TALK_XMLLITE_CONSTANTS_H_
diff --git a/talk/xmllite/xmlelement.cc b/talk/xmllite/xmlelement.cc
new file mode 100644
index 0000000..3ec085c
--- /dev/null
+++ b/talk/xmllite/xmlelement.cc
@@ -0,0 +1,520 @@
+/*
+ * 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 <string>
+#include <iostream>
+#include <vector>
+#include <sstream>
+
+#include "talk/base/common.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlparser.h"
+#include "talk/xmllite/xmlbuilder.h"
+#include "talk/xmllite/xmlprinter.h"
+#include "talk/xmllite/xmlconstants.h"
+
+namespace buzz {
+
+const QName QN_EMPTY(true, STR_EMPTY, STR_EMPTY);
+const QName QN_XMLNS(true, STR_EMPTY, STR_XMLNS);
+
+
+XmlChild::~XmlChild() {
+}
+
+bool
+XmlText::IsTextImpl() const {
+  return true;
+}
+
+XmlElement *
+XmlText::AsElementImpl() const {
+  return NULL;
+}
+
+XmlText *
+XmlText::AsTextImpl() const {
+  return const_cast<XmlText *>(this);
+}
+
+void
+XmlText::SetText(const std::string & text) {
+  text_ = text;
+}
+
+void
+XmlText::AddParsedText(const char * buf, int len) {
+  text_.append(buf, len);
+}
+
+void
+XmlText::AddText(const std::string & text) {
+  text_ += text;
+}
+
+XmlText::~XmlText() {
+}
+
+XmlElement::XmlElement(const QName & name) :
+    name_(name),
+    pFirstAttr_(NULL),
+    pLastAttr_(NULL),
+    pFirstChild_(NULL),
+    pLastChild_(NULL),
+    cdata_(false) {
+}
+
+XmlElement::XmlElement(const XmlElement & elt) :
+    XmlChild(),
+    name_(elt.name_),
+    pFirstAttr_(NULL),
+    pLastAttr_(NULL),
+    pFirstChild_(NULL),
+    pLastChild_(NULL),
+    cdata_(false) {
+
+  // copy attributes
+  XmlAttr * pAttr;
+  XmlAttr ** ppLastAttr = &pFirstAttr_;
+  XmlAttr * newAttr = NULL;
+  for (pAttr = elt.pFirstAttr_; pAttr; pAttr = pAttr->NextAttr()) {
+    newAttr = new XmlAttr(*pAttr);
+    *ppLastAttr = newAttr;
+    ppLastAttr = &(newAttr->pNextAttr_);
+  }
+  pLastAttr_ = newAttr;
+
+  // copy children
+  XmlChild * pChild;
+  XmlChild ** ppLast = &pFirstChild_;
+  XmlChild * newChild = NULL;
+
+  for (pChild = elt.pFirstChild_; pChild; pChild = pChild->NextChild()) {
+    if (pChild->IsText()) {
+      newChild = new XmlText(*(pChild->AsText()));
+    } else {
+      newChild = new XmlElement(*(pChild->AsElement()));
+    }
+    *ppLast = newChild;
+    ppLast = &(newChild->pNextChild_);
+  }
+  pLastChild_ = newChild;
+
+  cdata_ = elt.cdata_;
+}
+
+XmlElement::XmlElement(const QName & name, bool useDefaultNs) :
+  name_(name),
+  pFirstAttr_(useDefaultNs ? new XmlAttr(QN_XMLNS, name.Namespace()) : NULL),
+  pLastAttr_(pFirstAttr_),
+  pFirstChild_(NULL),
+  pLastChild_(NULL),
+  cdata_(false) {
+}
+
+bool
+XmlElement::IsTextImpl() const {
+  return false;
+}
+
+XmlElement *
+XmlElement::AsElementImpl() const {
+  return const_cast<XmlElement *>(this);
+}
+
+XmlText *
+XmlElement::AsTextImpl() const {
+  return NULL;
+}
+
+const std::string &
+XmlElement::BodyText() const {
+  if (pFirstChild_ && pFirstChild_->IsText() && pLastChild_ == pFirstChild_) {
+    return pFirstChild_->AsText()->Text();
+  }
+
+  return STR_EMPTY;
+}
+
+void
+XmlElement::SetBodyText(const std::string & text) {
+  if (text == STR_EMPTY) {
+    ClearChildren();
+  } else if (pFirstChild_ == NULL) {
+    AddText(text);
+  } else if (pFirstChild_->IsText() && pLastChild_ == pFirstChild_) {
+    pFirstChild_->AsText()->SetText(text);
+  } else {
+    ClearChildren();
+    AddText(text);
+  }
+}
+
+const QName &
+XmlElement::FirstElementName() const {
+  const XmlElement * element = FirstElement();
+  if (element == NULL)
+    return QN_EMPTY;
+  return element->Name();
+}
+
+XmlAttr *
+XmlElement::FirstAttr() {
+  return pFirstAttr_;
+}
+
+const std::string &
+XmlElement::Attr(const QName & name) const {
+  XmlAttr * pattr;
+  for (pattr = pFirstAttr_; pattr; pattr = pattr->pNextAttr_) {
+    if (pattr->name_ == name)
+      return pattr->value_;
+  }
+  return STR_EMPTY;
+}
+
+bool
+XmlElement::HasAttr(const QName & name) const {
+  XmlAttr * pattr;
+  for (pattr = pFirstAttr_; pattr; pattr = pattr->pNextAttr_) {
+    if (pattr->name_ == name)
+      return true;
+  }
+  return false;
+}
+
+void
+XmlElement::SetAttr(const QName & name, const std::string & value) {
+  XmlAttr * pattr;
+  for (pattr = pFirstAttr_; pattr; pattr = pattr->pNextAttr_) {
+    if (pattr->name_ == name)
+      break;
+  }
+  if (!pattr) {
+    pattr = new XmlAttr(name, value);
+    if (pLastAttr_)
+      pLastAttr_->pNextAttr_ = pattr;
+    else
+      pFirstAttr_ = pattr;
+    pLastAttr_ = pattr;
+    return;
+  }
+  pattr->value_ = value;
+}
+
+void
+XmlElement::ClearAttr(const QName & name) {
+  XmlAttr * pattr;
+  XmlAttr *pLastAttr = NULL;
+  for (pattr = pFirstAttr_; pattr; pattr = pattr->pNextAttr_) {
+    if (pattr->name_ == name)
+      break;
+    pLastAttr = pattr;
+  }
+  if (!pattr)
+    return;
+  if (!pLastAttr)
+    pFirstAttr_ = pattr->pNextAttr_;
+  else
+    pLastAttr->pNextAttr_ = pattr->pNextAttr_;
+  if (pLastAttr_ == pattr)
+    pLastAttr_ = pLastAttr;
+  delete pattr;
+}
+
+XmlChild *
+XmlElement::FirstChild() {
+  return pFirstChild_;
+}
+
+XmlElement *
+XmlElement::FirstElement() {
+  XmlChild * pChild;
+  for (pChild = pFirstChild_; pChild; pChild = pChild->pNextChild_) {
+    if (!pChild->IsText())
+      return pChild->AsElement();
+  }
+  return NULL;
+}
+
+XmlElement *
+XmlElement::NextElement() {
+  XmlChild * pChild;
+  for (pChild = pNextChild_; pChild; pChild = pChild->pNextChild_) {
+    if (!pChild->IsText())
+      return pChild->AsElement();
+  }
+  return NULL;
+}
+
+XmlElement *
+XmlElement::FirstWithNamespace(const std::string & ns) {
+  XmlChild * pChild;
+  for (pChild = pFirstChild_; pChild; pChild = pChild->pNextChild_) {
+    if (!pChild->IsText() && pChild->AsElement()->Name().Namespace() == ns)
+      return pChild->AsElement();
+  }
+  return NULL;
+}
+
+XmlElement *
+XmlElement::NextWithNamespace(const std::string & ns) {
+  XmlChild * pChild;
+  for (pChild = pNextChild_; pChild; pChild = pChild->pNextChild_) {
+    if (!pChild->IsText() && pChild->AsElement()->Name().Namespace() == ns)
+      return pChild->AsElement();
+  }
+  return NULL;
+}
+
+XmlElement *
+XmlElement::FirstNamed(const QName & name) {
+  XmlChild * pChild;
+  for (pChild = pFirstChild_; pChild; pChild = pChild->pNextChild_) {
+    if (!pChild->IsText() && pChild->AsElement()->Name() == name)
+      return pChild->AsElement();
+  }
+  return NULL;
+}
+
+XmlElement *
+XmlElement::NextNamed(const QName & name) {
+  XmlChild * pChild;
+  for (pChild = pNextChild_; pChild; pChild = pChild->pNextChild_) {
+    if (!pChild->IsText() && pChild->AsElement()->Name() == name)
+      return pChild->AsElement();
+  }
+  return NULL;
+}
+
+XmlElement* XmlElement::FindOrAddNamedChild(const QName& name) {
+  XmlElement* child = FirstNamed(name);
+  if (!child) {
+    child = new XmlElement(name);
+    AddElement(child);
+  }
+
+  return child;
+}
+
+const std::string &
+XmlElement::TextNamed(const QName & name) const {
+  XmlChild * pChild;
+  for (pChild = pFirstChild_; pChild; pChild = pChild->pNextChild_) {
+    if (!pChild->IsText() && pChild->AsElement()->Name() == name)
+      return pChild->AsElement()->BodyText();
+  }
+  return STR_EMPTY;
+}
+
+void
+XmlElement::InsertChildAfter(XmlChild * pPredecessor, XmlChild * pNext) {
+  if (pPredecessor == NULL) {
+    pNext->pNextChild_ = pFirstChild_;
+    pFirstChild_ = pNext;
+  }
+  else {
+    pNext->pNextChild_ = pPredecessor->pNextChild_;
+    pPredecessor->pNextChild_ = pNext;
+  }
+}
+
+void
+XmlElement::RemoveChildAfter(XmlChild * pPredecessor) {
+  XmlChild * pNext;
+
+  if (pPredecessor == NULL) {
+    pNext = pFirstChild_;
+    pFirstChild_ = pNext->pNextChild_;
+  }
+  else {
+    pNext = pPredecessor->pNextChild_;
+    pPredecessor->pNextChild_ = pNext->pNextChild_;
+  }
+
+  if (pLastChild_ == pNext)
+    pLastChild_ = pPredecessor;
+
+  delete pNext;
+}
+
+void
+XmlElement::AddAttr(const QName & name, const std::string & value) {
+  ASSERT(!HasAttr(name));
+
+  XmlAttr ** pprev = pLastAttr_ ? &(pLastAttr_->pNextAttr_) : &pFirstAttr_;
+  pLastAttr_ = (*pprev = new XmlAttr(name, value));
+}
+
+void
+XmlElement::AddAttr(const QName & name, const std::string & value,
+                         int depth) {
+  XmlElement * element = this;
+  while (depth--) {
+    element = element->pLastChild_->AsElement();
+  }
+  element->AddAttr(name, value);
+}
+
+void
+XmlElement::AddParsedText(const char * cstr, int len) {
+  if (len == 0)
+    return;
+
+  if (pLastChild_ && pLastChild_->IsText()) {
+    pLastChild_->AsText()->AddParsedText(cstr, len);
+    return;
+  }
+  XmlChild ** pprev = pLastChild_ ? &(pLastChild_->pNextChild_) : &pFirstChild_;
+  pLastChild_ = *pprev = new XmlText(cstr, len);
+}
+
+void
+XmlElement::AddCDATAText(const char * buf, int len) {
+  cdata_ = true;
+  AddParsedText(buf, len);
+}
+
+void
+XmlElement::AddText(const std::string & text) {
+  if (text == STR_EMPTY)
+    return;
+
+  if (pLastChild_ && pLastChild_->IsText()) {
+    pLastChild_->AsText()->AddText(text);
+    return;
+  }
+  XmlChild ** pprev = pLastChild_ ? &(pLastChild_->pNextChild_) : &pFirstChild_;
+  pLastChild_ = *pprev = new XmlText(text);
+}
+
+void
+XmlElement::AddText(const std::string & text, int depth) {
+  // note: the first syntax is ambigious for msvc 6
+  // XmlElement * pel(this);
+  XmlElement * element = this;
+  while (depth--) {
+    element = element->pLastChild_->AsElement();
+  }
+  element->AddText(text);
+}
+
+void
+XmlElement::AddElement(XmlElement *pelChild) {
+  if (pelChild == NULL)
+    return;
+
+  XmlChild ** pprev = pLastChild_ ? &(pLastChild_->pNextChild_) : &pFirstChild_;
+  pLastChild_ = *pprev = pelChild;
+  pelChild->pNextChild_ = NULL;
+}
+
+void
+XmlElement::AddElement(XmlElement *pelChild, int depth) {
+  XmlElement * element = this;
+  while (depth--) {
+    element = element->pLastChild_->AsElement();
+  }
+  element->AddElement(pelChild);
+}
+
+void
+XmlElement::ClearNamedChildren(const QName & name) {
+  XmlChild * prev_child = NULL;
+  XmlChild * next_child;
+  XmlChild * child;
+  for (child = FirstChild(); child; child = next_child) {
+    next_child = child->NextChild();
+    if (!child->IsText() && child->AsElement()->Name() == name)
+    {
+      RemoveChildAfter(prev_child);
+      continue;
+    }
+    prev_child = child;
+  }
+}
+
+void
+XmlElement::ClearAttributes() {
+  XmlAttr * pattr;
+  for (pattr = pFirstAttr_; pattr; ) {
+    XmlAttr * pToDelete = pattr;
+    pattr = pattr->pNextAttr_;
+    delete pToDelete;
+  }
+  pFirstAttr_ = pLastAttr_ = NULL;
+}
+
+void
+XmlElement::ClearChildren() {
+  XmlChild * pchild;
+  for (pchild = pFirstChild_; pchild; ) {
+    XmlChild * pToDelete = pchild;
+    pchild = pchild->pNextChild_;
+    delete pToDelete;
+  }
+  pFirstChild_ = pLastChild_ = NULL;
+}
+
+std::string
+XmlElement::Str() const {
+  std::stringstream ss;
+  Print(&ss, NULL, 0);
+  return ss.str();
+}
+
+XmlElement *
+XmlElement::ForStr(const std::string & str) {
+  XmlBuilder builder;
+  XmlParser::ParseXml(&builder, str);
+  return builder.CreateElement();
+}
+
+void
+XmlElement::Print(
+    std::ostream * pout, std::string xmlns[], int xmlnsCount) const {
+  XmlPrinter::PrintXml(pout, this, xmlns, xmlnsCount);
+}
+
+XmlElement::~XmlElement() {
+  XmlAttr * pattr;
+  for (pattr = pFirstAttr_; pattr; ) {
+    XmlAttr * pToDelete = pattr;
+    pattr = pattr->pNextAttr_;
+    delete pToDelete;
+  }
+
+  XmlChild * pchild;
+  for (pchild = pFirstChild_; pchild; ) {
+    XmlChild * pToDelete = pchild;
+    pchild = pchild->pNextChild_;
+    delete pToDelete;
+  }
+}
+
+}
diff --git a/talk/xmllite/xmlelement.h b/talk/xmllite/xmlelement.h
new file mode 100644
index 0000000..38e31a9
--- /dev/null
+++ b/talk/xmllite/xmlelement.h
@@ -0,0 +1,239 @@
+/*
+ * 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 _xmlelement_h_
+#define _xmlelement_h_
+
+#include <iosfwd>
+#include <string>
+#include "talk/base/scoped_ptr.h"
+#include "talk/xmllite/qname.h"
+
+namespace buzz {
+
+extern const QName QN_EMPTY;
+extern const QName QN_XMLNS;
+
+
+class XmlChild;
+class XmlText;
+class XmlElement;
+class XmlAttr;
+
+class XmlChild {
+friend class XmlElement;
+
+public:
+
+  XmlChild * NextChild() { return pNextChild_; }
+  const XmlChild * NextChild() const { return pNextChild_; }
+
+  bool IsText() const { return IsTextImpl(); }
+
+  XmlElement * AsElement() { return AsElementImpl(); }
+  const XmlElement * AsElement() const { return AsElementImpl(); }
+
+  XmlText * AsText() { return AsTextImpl(); }
+  const XmlText * AsText() const { return AsTextImpl(); }
+
+
+protected:
+
+  XmlChild() :
+    pNextChild_(NULL) {
+  }
+
+  virtual bool IsTextImpl() const = 0;
+  virtual XmlElement * AsElementImpl() const = 0;
+  virtual XmlText * AsTextImpl() const = 0;
+
+
+  virtual ~XmlChild();
+
+private:
+  XmlChild(const XmlChild & noimpl);
+
+  XmlChild * pNextChild_;
+
+};
+
+class XmlText : public XmlChild {
+public:
+  explicit XmlText(const std::string & text) :
+    XmlChild(),
+    text_(text) {
+  }
+  explicit XmlText(const XmlText & t) :
+    XmlChild(),
+    text_(t.text_) {
+  }
+  explicit XmlText(const char * cstr, size_t len) :
+    XmlChild(),
+    text_(cstr, len) {
+  }
+  virtual ~XmlText();
+
+  const std::string & Text() const { return text_; }
+  void SetText(const std::string & text);
+  void AddParsedText(const char * buf, int len);
+  void AddText(const std::string & text);
+
+protected:
+  virtual bool IsTextImpl() const;
+  virtual XmlElement * AsElementImpl() const;
+  virtual XmlText * AsTextImpl() const;
+
+private:
+  std::string text_;
+};
+
+class XmlAttr {
+friend class XmlElement;
+
+public:
+  XmlAttr * NextAttr() const { return pNextAttr_; }
+  const QName & Name() const { return name_; }
+  const std::string & Value() const { return value_; }
+
+private:
+  explicit XmlAttr(const QName & name, const std::string & value) :
+    pNextAttr_(NULL),
+    name_(name),
+    value_(value) {
+  }
+  explicit XmlAttr(const XmlAttr & att) :
+    pNextAttr_(NULL),
+    name_(att.name_),
+    value_(att.value_) {
+  }
+
+  XmlAttr * pNextAttr_;
+  QName name_;
+  std::string value_;
+};
+
+class XmlElement : public XmlChild {
+public:
+  explicit XmlElement(const QName & name);
+  explicit XmlElement(const QName & name, bool useDefaultNs);
+  explicit XmlElement(const XmlElement & elt);
+
+  virtual ~XmlElement();
+
+  const QName& Name() const { return name_; }
+  void SetName(const QName& name) { name_ = name; }
+
+  const std::string & BodyText() const;
+  void SetBodyText(const std::string & text);
+
+  const QName & FirstElementName() const;
+
+  XmlAttr * FirstAttr();
+  const XmlAttr * FirstAttr() const
+    { return const_cast<XmlElement *>(this)->FirstAttr(); }
+
+  //! Attr will return STR_EMPTY if the attribute isn't there:
+  //! use HasAttr to test presence of an attribute. 
+  const std::string & Attr(const QName & name) const;
+  bool HasAttr(const QName & name) const;
+  void SetAttr(const QName & name, const std::string & value);
+  void ClearAttr(const QName & name);
+
+  XmlChild * FirstChild();
+  const XmlChild * FirstChild() const
+    { return const_cast<XmlElement *>(this)->FirstChild(); }
+
+  XmlElement * FirstElement();
+  const XmlElement * FirstElement() const
+    { return const_cast<XmlElement *>(this)->FirstElement(); }
+
+  XmlElement * NextElement();
+  const XmlElement * NextElement() const
+    { return const_cast<XmlElement *>(this)->NextElement(); }
+
+  XmlElement * FirstWithNamespace(const std::string & ns);
+  const XmlElement * FirstWithNamespace(const std::string & ns) const
+    { return const_cast<XmlElement *>(this)->FirstWithNamespace(ns); }
+
+  XmlElement * NextWithNamespace(const std::string & ns);
+  const XmlElement * NextWithNamespace(const std::string & ns) const
+    { return const_cast<XmlElement *>(this)->NextWithNamespace(ns); }
+
+  XmlElement * FirstNamed(const QName & name);
+  const XmlElement * FirstNamed(const QName & name) const
+    { return const_cast<XmlElement *>(this)->FirstNamed(name); }
+
+  XmlElement * NextNamed(const QName & name);
+  const XmlElement * NextNamed(const QName & name) const
+    { return const_cast<XmlElement *>(this)->NextNamed(name); }
+
+  // Finds the first element named 'name'.  If that element can't be found then
+  // adds one and returns it.
+  XmlElement* FindOrAddNamedChild(const QName& name);
+
+  const std::string & TextNamed(const QName & name) const;
+
+  void InsertChildAfter(XmlChild * pPredecessor, XmlChild * pNewChild);
+  void RemoveChildAfter(XmlChild * pPredecessor);
+
+  void AddParsedText(const char * buf, int len);
+  // Note: CDATA is not supported by XMPP, therefore using this function will
+  // generate non-XMPP compatible XML.
+  void AddCDATAText(const char * buf, int len);
+  void AddText(const std::string & text);
+  void AddText(const std::string & text, int depth);
+  void AddElement(XmlElement * pelChild);
+  void AddElement(XmlElement * pelChild, int depth);
+  void AddAttr(const QName & name, const std::string & value);
+  void AddAttr(const QName & name, const std::string & value, int depth);
+  void ClearNamedChildren(const QName & name);
+  void ClearAttributes();
+  void ClearChildren();
+
+  static XmlElement * ForStr(const std::string & str);
+  std::string Str() const;
+
+  void Print(std::ostream * pout, std::string xmlns[], int xmlnsCount) const;
+
+  bool IsCDATA() const { return cdata_; }
+
+protected:
+  virtual bool IsTextImpl() const;
+  virtual XmlElement * AsElementImpl() const;
+  virtual XmlText * AsTextImpl() const;
+
+private:
+  QName name_;
+  XmlAttr * pFirstAttr_;
+  XmlAttr * pLastAttr_;
+  XmlChild * pFirstChild_;
+  XmlChild * pLastChild_;
+  bool cdata_;
+};
+
+}
+#endif
diff --git a/talk/xmllite/xmlnsstack.cc b/talk/xmllite/xmlnsstack.cc
new file mode 100644
index 0000000..18e1607
--- /dev/null
+++ b/talk/xmllite/xmlnsstack.cc
@@ -0,0 +1,204 @@
+/*
+ * 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 <string>
+#include <iostream>
+#include <vector>
+#include <sstream>
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmllite/xmlnsstack.h"
+#include "talk/xmllite/xmlconstants.h"
+
+namespace buzz {
+
+XmlnsStack::XmlnsStack() :
+  pxmlnsStack_(new std::vector<std::string>),
+  pxmlnsDepthStack_(new std::vector<size_t>) {
+}
+
+XmlnsStack::~XmlnsStack() {}
+
+void
+XmlnsStack::PushFrame() {
+  pxmlnsDepthStack_->push_back(pxmlnsStack_->size());
+}
+
+void
+XmlnsStack::PopFrame() {
+  size_t prev_size = pxmlnsDepthStack_->back();
+  pxmlnsDepthStack_->pop_back();
+  if (prev_size < pxmlnsStack_->size()) {
+    pxmlnsStack_->erase(pxmlnsStack_->begin() + prev_size,
+                        pxmlnsStack_->end());
+  }
+}
+const std::pair<std::string, bool> NS_NOT_FOUND(STR_EMPTY, false);
+const std::pair<std::string, bool> EMPTY_NS_FOUND(STR_EMPTY, true);
+const std::pair<std::string, bool> XMLNS_DEFINITION_FOUND(NS_XMLNS, true);
+
+const std::string *
+XmlnsStack::NsForPrefix(const std::string & prefix) {
+  if (prefix.length() >= 3 &&
+      (prefix[0] == 'x' || prefix[0] == 'X') &&
+      (prefix[1] == 'm' || prefix[1] == 'M') &&
+      (prefix[2] == 'l' || prefix[2] == 'L')) {
+    if (prefix == "xml")
+      return &(NS_XML);
+    if (prefix == "xmlns")
+      return &(NS_XMLNS);
+    return NULL;
+  }
+
+  std::vector<std::string>::iterator pos;
+  for (pos = pxmlnsStack_->end(); pos > pxmlnsStack_->begin(); ) {
+    pos -= 2;
+    if (*pos == prefix)
+      return &(*(pos + 1));
+  }
+
+  if (prefix == STR_EMPTY)
+    return &(STR_EMPTY); // default namespace
+
+  return NULL; // none found
+}
+
+bool
+XmlnsStack::PrefixMatchesNs(const std::string & prefix, const std::string & ns) {
+  const std::string * match = NsForPrefix(prefix);
+  if (match == NULL)
+    return false;
+  return (*match == ns);
+}
+
+std::pair<std::string, bool>
+XmlnsStack::PrefixForNs(const std::string & ns, bool isattr) {
+  if (ns == NS_XML)
+    return std::make_pair(std::string("xml"), true);
+  if (ns == NS_XMLNS)
+    return std::make_pair(std::string("xmlns"), true);
+  if (isattr ? ns == STR_EMPTY : PrefixMatchesNs(STR_EMPTY, ns))
+    return std::make_pair(STR_EMPTY, true);
+
+  std::vector<std::string>::iterator pos;
+  for (pos = pxmlnsStack_->end(); pos > pxmlnsStack_->begin(); ) {
+    pos -= 2;
+    if (*(pos + 1) == ns &&
+        (!isattr || !pos->empty()) && PrefixMatchesNs(*pos, ns))
+      return std::make_pair(*pos, true);
+  }
+
+  return std::make_pair(STR_EMPTY, false); // none found
+}
+
+std::string
+XmlnsStack::FormatQName(const QName & name, bool isAttr) {
+  std::string prefix(PrefixForNs(name.Namespace(), isAttr).first);
+  if (prefix == STR_EMPTY)
+    return name.LocalPart();
+  else
+    return prefix + ':' + name.LocalPart();
+}
+
+void
+XmlnsStack::AddXmlns(const std::string & prefix, const std::string & ns) {
+  pxmlnsStack_->push_back(prefix);
+  pxmlnsStack_->push_back(ns);
+}
+
+void
+XmlnsStack::RemoveXmlns() {
+  pxmlnsStack_->pop_back();
+  pxmlnsStack_->pop_back();
+}
+
+static bool IsAsciiLetter(char ch) {
+  return ((ch >= 'a' && ch <= 'z') ||
+          (ch >= 'A' && ch <= 'Z'));
+}
+
+static std::string AsciiLower(const std::string & s) {
+  std::string result(s);
+  size_t i;
+  for (i = 0; i < result.length(); i++) {
+    if (result[i] >= 'A' && result[i] <= 'Z')
+      result[i] += 'a' - 'A';
+  }
+  return result;
+}
+
+static std::string SuggestPrefix(const std::string & ns) {
+  size_t len = ns.length();
+  size_t i = ns.find_last_of('.');
+  if (i != std::string::npos && len - i <= 4 + 1)
+    len = i; // chop off ".html" or ".xsd" or ".?{0,4}"
+  size_t last = len;
+  while (last > 0) {
+    last -= 1;
+    if (IsAsciiLetter(ns[last])) {
+      size_t first = last;
+      last += 1;
+      while (first > 0) {
+        if (!IsAsciiLetter(ns[first - 1]))
+          break;
+        first -= 1;
+      }
+      if (last - first > 4)
+        last = first + 3;
+      std::string candidate(AsciiLower(ns.substr(first, last - first)));
+      if (candidate.find("xml") != 0)
+        return candidate;
+      break;
+    }
+  }
+  return "ns";
+}
+
+
+std::pair<std::string, bool>
+XmlnsStack::AddNewPrefix(const std::string & ns, bool isAttr) {
+  if (PrefixForNs(ns, isAttr).second)
+    return std::make_pair(STR_EMPTY, false);
+
+  std::string base(SuggestPrefix(ns));
+  std::string result(base);
+  int i = 2;
+  while (NsForPrefix(result) != NULL) {
+    std::stringstream ss;
+    ss << base;
+    ss << (i++);
+    ss >> result;
+  }
+  AddXmlns(result, ns);
+  return std::make_pair(result, true);
+}
+
+void XmlnsStack::Reset() {
+  pxmlnsStack_->clear();
+  pxmlnsDepthStack_->clear();
+}
+
+}
diff --git a/talk/xmllite/xmlnsstack.h b/talk/xmllite/xmlnsstack.h
new file mode 100644
index 0000000..c7e9f89
--- /dev/null
+++ b/talk/xmllite/xmlnsstack.h
@@ -0,0 +1,62 @@
+/*
+ * 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 _xmlnsstack_h_
+#define _xmlnsstack_h_
+
+#include <string>
+#include <vector>
+#include "talk/base/scoped_ptr.h"
+#include "talk/xmllite/qname.h"
+
+namespace buzz {
+
+class XmlnsStack {
+public:
+  XmlnsStack();
+  ~XmlnsStack();
+
+  void AddXmlns(const std::string & prefix, const std::string & ns);
+  void RemoveXmlns();
+  void PushFrame();
+  void PopFrame();
+  void Reset();
+
+  const std::string * NsForPrefix(const std::string & prefix);
+  bool PrefixMatchesNs(const std::string & prefix, const std::string & ns);
+  std::pair<std::string, bool> PrefixForNs(const std::string & ns, bool isAttr);
+  std::pair<std::string, bool> AddNewPrefix(const std::string & ns, bool isAttr);
+  std::string FormatQName(const QName & name, bool isAttr);
+
+private:
+
+  talk_base::scoped_ptr<std::vector<std::string> > pxmlnsStack_;
+  talk_base::scoped_ptr<std::vector<size_t> > pxmlnsDepthStack_;
+};
+}
+
+#endif
diff --git a/talk/xmllite/xmlparser.cc b/talk/xmllite/xmlparser.cc
new file mode 100644
index 0000000..568bce1
--- /dev/null
+++ b/talk/xmllite/xmlparser.cc
@@ -0,0 +1,292 @@
+/*
+ * 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/xmllite/xmlparser.h"
+
+#include <string>
+#include <vector>
+#include <iostream>
+#include "talk/base/common.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmllite/xmlnsstack.h"
+#include "talk/xmllite/xmlconstants.h"
+#include "talk/xmllite/xmlnsstack.h"
+#ifdef EXPAT_RELATIVE_PATH
+#include "lib/expat.h"
+#else
+#include "third_party/expat/v2_0_1/Source/lib/expat.h"
+#endif  // EXPAT_RELATIVE_PATH
+
+namespace buzz {
+
+
+static void
+StartElementCallback(void * userData, const char *name, const char **atts) {
+  (static_cast<XmlParser *>(userData))->ExpatStartElement(name, atts);
+}
+
+static void
+EndElementCallback(void * userData, const char *name) {
+  (static_cast<XmlParser *>(userData))->ExpatEndElement(name);
+}
+
+static void
+CharacterDataCallback(void * userData, const char *text, int len) {
+  (static_cast<XmlParser *>(userData))->ExpatCharacterData(text, len);
+}
+
+static void
+XmlDeclCallback(void * userData, const char * ver, const char * enc, int st) {
+  (static_cast<XmlParser *>(userData))->ExpatXmlDecl(ver, enc, st);
+}
+
+XmlParser::XmlParser(XmlParseHandler *pxph) :
+    context_(this), pxph_(pxph), sentError_(false) {
+  expat_ = XML_ParserCreate(NULL);
+  XML_SetUserData(expat_, this);
+  XML_SetElementHandler(expat_, StartElementCallback, EndElementCallback);
+  XML_SetCharacterDataHandler(expat_, CharacterDataCallback);
+  XML_SetXmlDeclHandler(expat_, XmlDeclCallback);
+}
+
+void
+XmlParser::Reset() {
+  if (!XML_ParserReset(expat_, NULL)) {
+    XML_ParserFree(expat_);
+    expat_ = XML_ParserCreate(NULL);
+  }
+  XML_SetUserData(expat_, this);
+  XML_SetElementHandler(expat_, StartElementCallback, EndElementCallback);
+  XML_SetCharacterDataHandler(expat_, CharacterDataCallback);
+  XML_SetXmlDeclHandler(expat_, XmlDeclCallback);
+  context_.Reset();
+  sentError_ = false;
+}
+
+static bool
+XmlParser_StartsWithXmlns(const char *name) {
+  return name[0] == 'x' &&
+         name[1] == 'm' &&
+         name[2] == 'l' &&
+         name[3] == 'n' &&
+         name[4] == 's';
+}
+
+void
+XmlParser::ExpatStartElement(const char *name, const char **atts) {
+  if (context_.RaisedError() != XML_ERROR_NONE)
+    return;
+  const char **att;
+  context_.StartElement();
+  for (att = atts; *att; att += 2) {
+    if (XmlParser_StartsWithXmlns(*att)) {
+      if ((*att)[5] == '\0') {
+        context_.StartNamespace("", *(att + 1));
+      }
+      else if ((*att)[5] == ':') {
+        if (**(att + 1) == '\0') {
+          // In XML 1.0 empty namespace illegal with prefix (not in 1.1)
+          context_.RaiseError(XML_ERROR_SYNTAX);
+          return;
+        }
+        context_.StartNamespace((*att) + 6, *(att + 1));
+      }
+    }
+  }
+  context_.SetPosition(XML_GetCurrentLineNumber(expat_),
+                       XML_GetCurrentColumnNumber(expat_),
+                       XML_GetCurrentByteIndex(expat_));
+  pxph_->StartElement(&context_, name, atts);
+}
+
+void
+XmlParser::ExpatEndElement(const char *name) {
+  if (context_.RaisedError() != XML_ERROR_NONE)
+    return;
+  context_.EndElement();
+  context_.SetPosition(XML_GetCurrentLineNumber(expat_),
+                       XML_GetCurrentColumnNumber(expat_),
+                       XML_GetCurrentByteIndex(expat_));
+  pxph_->EndElement(&context_, name);
+}
+
+void
+XmlParser::ExpatCharacterData(const char *text, int len) {
+  if (context_.RaisedError() != XML_ERROR_NONE)
+    return;
+  context_.SetPosition(XML_GetCurrentLineNumber(expat_),
+                       XML_GetCurrentColumnNumber(expat_),
+                       XML_GetCurrentByteIndex(expat_));
+  pxph_->CharacterData(&context_, text, len);
+}
+
+void
+XmlParser::ExpatXmlDecl(const char * ver, const char * enc, int standalone) {
+  if (context_.RaisedError() != XML_ERROR_NONE)
+    return;
+
+  if (ver && std::string("1.0") != ver) {
+    context_.RaiseError(XML_ERROR_SYNTAX);
+    return;
+  }
+
+  if (standalone == 0) {
+    context_.RaiseError(XML_ERROR_SYNTAX);
+    return;
+  }
+
+  if (enc && !((enc[0] == 'U' || enc[0] == 'u') &&
+               (enc[1] == 'T' || enc[1] == 't') &&
+               (enc[2] == 'F' || enc[2] == 'f') &&
+                enc[3] == '-' && enc[4] =='8')) {
+    context_.RaiseError(XML_ERROR_INCORRECT_ENCODING);
+    return;
+  }
+
+}
+
+bool
+XmlParser::Parse(const char *data, size_t len, bool isFinal) {
+  if (sentError_)
+    return false;
+
+  if (XML_Parse(expat_, data, static_cast<int>(len), isFinal) !=
+      XML_STATUS_OK) {
+    context_.SetPosition(XML_GetCurrentLineNumber(expat_),
+                         XML_GetCurrentColumnNumber(expat_),
+                         XML_GetCurrentByteIndex(expat_));
+    context_.RaiseError(XML_GetErrorCode(expat_));
+  }
+
+  if (context_.RaisedError() != XML_ERROR_NONE) {
+    sentError_ = true;
+    pxph_->Error(&context_, context_.RaisedError());
+    return false;
+  }
+
+  return true;
+}
+
+XmlParser::~XmlParser() {
+  XML_ParserFree(expat_);
+}
+
+void
+XmlParser::ParseXml(XmlParseHandler *pxph, std::string text) {
+  XmlParser parser(pxph);
+  parser.Parse(text.c_str(), text.length(), true);
+}
+
+XmlParser::ParseContext::ParseContext(XmlParser *parser) :
+    parser_(parser),
+    xmlnsstack_(),
+    raised_(XML_ERROR_NONE),
+    line_number_(0),
+    column_number_(0),
+    byte_index_(0) {
+}
+
+void
+XmlParser::ParseContext::StartNamespace(const char *prefix, const char *ns) {
+  xmlnsstack_.AddXmlns(
+    *prefix ? std::string(prefix) : STR_EMPTY,
+//    ns == NS_CLIENT ? NS_CLIENT :
+//    ns == NS_ROSTER ? NS_ROSTER :
+//    ns == NS_GR ? NS_GR :
+    std::string(ns));
+}
+
+void
+XmlParser::ParseContext::StartElement() {
+  xmlnsstack_.PushFrame();
+}
+
+void
+XmlParser::ParseContext::EndElement() {
+  xmlnsstack_.PopFrame();
+}
+
+QName
+XmlParser::ParseContext::ResolveQName(const char *qname, bool isAttr) {
+  const char *c;
+  for (c = qname; *c; ++c) {
+    if (*c == ':') {
+      const std::string * result;
+      result = xmlnsstack_.NsForPrefix(std::string(qname, c - qname));
+      if (result == NULL)
+        return QN_EMPTY;
+      const char * localname = c + 1;
+      return QName(*result, localname);
+    }
+  }
+  if (isAttr) {
+    return QName(STR_EMPTY, qname);
+  }
+
+  const std::string * result;
+  result = xmlnsstack_.NsForPrefix(STR_EMPTY);
+  if (result == NULL)
+    return QN_EMPTY;
+
+  return QName(*result, qname);
+}
+
+void
+XmlParser::ParseContext::Reset() {
+  xmlnsstack_.Reset();
+  raised_ = XML_ERROR_NONE;
+}
+
+void
+XmlParser::ParseContext::SetPosition(int line, int column,
+                                          long byte_index) {
+  line_number_ = line;
+  column_number_ = column;
+  byte_index_ = byte_index;
+}
+
+void
+XmlParser::ParseContext::GetPosition(unsigned long * line,
+                                     unsigned long * column,
+                                     unsigned long * byte_index) {
+  if (line != NULL) {
+    *line = static_cast<unsigned long>(line_number_);
+  }
+
+  if (column != NULL) {
+    *column = static_cast<unsigned long>(column_number_);
+  }
+
+  if (byte_index != NULL) {
+    *byte_index = static_cast<unsigned long>(byte_index_);
+  }
+}
+
+XmlParser::ParseContext::~ParseContext() {
+}
+
+}
diff --git a/talk/xmllite/xmlparser.h b/talk/xmllite/xmlparser.h
new file mode 100644
index 0000000..3e85e35
--- /dev/null
+++ b/talk/xmllite/xmlparser.h
@@ -0,0 +1,121 @@
+/*
+ * 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 _xmlparser_h_
+#define _xmlparser_h_
+
+#include <string>
+
+#include "talk/xmllite/xmlnsstack.h"
+#ifdef EXPAT_RELATIVE_PATH
+#include "lib/expat.h"
+#else
+#include "third_party/expat/v2_0_1/Source/lib/expat.h"
+#endif  // EXPAT_RELATIVE_PATH
+
+struct XML_ParserStruct;
+typedef struct XML_ParserStruct* XML_Parser;
+
+namespace buzz {
+
+class XmlParseHandler;
+class XmlParseContext;
+class XmlParser;
+
+class XmlParseContext {
+public:
+  virtual ~XmlParseContext() {}
+  virtual QName ResolveQName(const char * qname, bool isAttr) = 0;
+  virtual void RaiseError(XML_Error err) = 0;
+  virtual void GetPosition(unsigned long * line, unsigned long * column,
+                           unsigned long * byte_index) = 0;
+};
+
+class XmlParseHandler {
+public:
+  virtual ~XmlParseHandler() {}
+  virtual void StartElement(XmlParseContext * pctx,
+               const char * name, const char ** atts) = 0;
+  virtual void EndElement(XmlParseContext * pctx,
+               const char * name) = 0;
+  virtual void CharacterData(XmlParseContext * pctx,
+               const char * text, int len) = 0;
+  virtual void Error(XmlParseContext * pctx,
+               XML_Error errorCode) = 0;
+};
+
+class XmlParser {
+public:
+  static void ParseXml(XmlParseHandler * pxph, std::string text);
+
+  explicit XmlParser(XmlParseHandler * pxph);
+  bool Parse(const char * data, size_t len, bool isFinal);
+  void Reset();
+  virtual ~XmlParser();
+
+  // expat callbacks
+  void ExpatStartElement(const char * name, const char ** atts);
+  void ExpatEndElement(const char * name);
+  void ExpatCharacterData(const char * text, int len);
+  void ExpatXmlDecl(const char * ver, const char * enc, int standalone);
+
+private:
+
+  class ParseContext : public XmlParseContext {
+  public:
+    ParseContext(XmlParser * parser);
+    virtual ~ParseContext();
+    virtual QName ResolveQName(const char * qname, bool isAttr);
+    virtual void RaiseError(XML_Error err) { if (!raised_) raised_ = err; }
+    virtual void GetPosition(unsigned long * line, unsigned long * column,
+                             unsigned long * byte_index);
+    XML_Error RaisedError() { return raised_; }
+    void Reset();
+
+    void StartElement();
+    void EndElement();
+    void StartNamespace(const char * prefix, const char * ns);
+    void SetPosition(int line, int column, long byte_index);
+
+  private:
+    const XmlParser * parser_;
+    XmlnsStack xmlnsstack_;
+    XML_Error raised_;
+    XML_Size line_number_;
+    XML_Size column_number_;
+    XML_Index byte_index_;
+  };
+
+  ParseContext context_;
+  XML_Parser expat_;
+  XmlParseHandler * pxph_;
+  bool sentError_;
+};
+
+}
+
+#endif
diff --git a/talk/xmllite/xmlprinter.cc b/talk/xmllite/xmlprinter.cc
new file mode 100644
index 0000000..b05898f
--- /dev/null
+++ b/talk/xmllite/xmlprinter.cc
@@ -0,0 +1,198 @@
+/*
+ * 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 <string>
+#include <iostream>
+#include <vector>
+#include <sstream>
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmllite/xmlprinter.h"
+#include "talk/xmllite/xmlnsstack.h"
+#include "talk/xmllite/xmlconstants.h"
+
+namespace buzz {
+
+class XmlPrinterImpl {
+public:
+  XmlPrinterImpl(std::ostream * pout,
+    const std::string * const xmlns, int xmlnsCount);
+  void PrintElement(const XmlElement * element);
+  void PrintQuotedValue(const std::string & text);
+  void PrintBodyText(const std::string & text);
+  void PrintCDATAText(const std::string & text);
+
+private:
+  std::ostream *pout_;
+  XmlnsStack xmlnsStack_;
+};
+
+void
+XmlPrinter::PrintXml(std::ostream * pout, const XmlElement * element) {
+  PrintXml(pout, element, NULL, 0);
+}
+
+void
+XmlPrinter::PrintXml(std::ostream * pout, const XmlElement * element,
+    const std::string * const xmlns, int xmlnsCount) {
+  XmlPrinterImpl printer(pout, xmlns, xmlnsCount);
+  printer.PrintElement(element);
+}
+
+XmlPrinterImpl::XmlPrinterImpl(std::ostream * pout,
+    const std::string * const xmlns, int xmlnsCount) :
+  pout_(pout),
+  xmlnsStack_() {
+  int i;
+  for (i = 0; i < xmlnsCount; i += 2) {
+    xmlnsStack_.AddXmlns(xmlns[i], xmlns[i + 1]);
+  }
+}
+
+void
+XmlPrinterImpl::PrintElement(const XmlElement * element) {
+  xmlnsStack_.PushFrame();
+
+  // first go through attrs of pel to add xmlns definitions
+  const XmlAttr * pattr;
+  for (pattr = element->FirstAttr(); pattr; pattr = pattr->NextAttr()) {
+    if (pattr->Name() == QN_XMLNS)
+      xmlnsStack_.AddXmlns(STR_EMPTY, pattr->Value());
+    else if (pattr->Name().Namespace() == NS_XMLNS)
+      xmlnsStack_.AddXmlns(pattr->Name().LocalPart(),
+        pattr->Value());
+  }
+
+  // then go through qnames to make sure needed xmlns definitons are added
+  std::vector<std::string> newXmlns;
+  std::pair<std::string, bool> prefix;
+  prefix = xmlnsStack_.AddNewPrefix(element->Name().Namespace(), false);
+  if (prefix.second) {
+    newXmlns.push_back(prefix.first);
+    newXmlns.push_back(element->Name().Namespace());
+  }
+
+  for (pattr = element->FirstAttr(); pattr; pattr = pattr->NextAttr()) {
+    prefix = xmlnsStack_.AddNewPrefix(pattr->Name().Namespace(), true);
+    if (prefix.second) {
+      newXmlns.push_back(prefix.first);
+      newXmlns.push_back(pattr->Name().Namespace());
+    }
+  }
+
+  // print the element name
+  *pout_ << '<' << xmlnsStack_.FormatQName(element->Name(), false);
+
+  // and the attributes
+  for (pattr = element->FirstAttr(); pattr; pattr = pattr->NextAttr()) {
+    *pout_ << ' ' << xmlnsStack_.FormatQName(pattr->Name(), true) << "=\"";
+    PrintQuotedValue(pattr->Value());
+    *pout_ << '"';
+  }
+
+  // and the extra xmlns declarations
+  std::vector<std::string>::iterator i(newXmlns.begin());
+  while (i < newXmlns.end()) {
+    if (*i == STR_EMPTY)
+      *pout_ << " xmlns=\"" << *(i + 1) << '"';
+    else
+      *pout_ << " xmlns:" << *i << "=\"" << *(i + 1) << '"';
+    i += 2;
+  }
+
+  // now the children
+  const XmlChild * pchild = element->FirstChild();
+
+  if (pchild == NULL)
+    *pout_ << "/>";
+  else {
+    *pout_ << '>';
+    while (pchild) {
+      if (pchild->IsText()) {
+        if (element->IsCDATA()) {
+          PrintCDATAText(pchild->AsText()->Text());
+        } else {
+          PrintBodyText(pchild->AsText()->Text());
+        }
+      } else
+        PrintElement(pchild->AsElement());
+      pchild = pchild->NextChild();
+    }
+    *pout_ << "</" << xmlnsStack_.FormatQName(element->Name(), false) << '>';
+  }
+
+  xmlnsStack_.PopFrame();
+}
+
+void
+XmlPrinterImpl::PrintQuotedValue(const std::string & text) {
+  size_t safe = 0;
+  for (;;) {
+    size_t unsafe = text.find_first_of("<>&\"", safe);
+    if (unsafe == std::string::npos)
+      unsafe = text.length();
+    *pout_ << text.substr(safe, unsafe - safe);
+    if (unsafe == text.length())
+      return;
+    switch (text[unsafe]) {
+      case '<': *pout_ << "&lt;"; break;
+      case '>': *pout_ << "&gt;"; break;
+      case '&': *pout_ << "&amp;"; break;
+      case '"': *pout_ << "&quot;"; break;
+    }
+    safe = unsafe + 1;
+    if (safe == text.length())
+      return;
+  }
+}
+
+void
+XmlPrinterImpl::PrintBodyText(const std::string & text) {
+  size_t safe = 0;
+  for (;;) {
+    size_t unsafe = text.find_first_of("<>&", safe);
+    if (unsafe == std::string::npos)
+      unsafe = text.length();
+    *pout_ << text.substr(safe, unsafe - safe);
+    if (unsafe == text.length())
+      return;
+    switch (text[unsafe]) {
+      case '<': *pout_ << "&lt;"; break;
+      case '>': *pout_ << "&gt;"; break;
+      case '&': *pout_ << "&amp;"; break;
+    }
+    safe = unsafe + 1;
+    if (safe == text.length())
+      return;
+  }
+}
+
+void
+XmlPrinterImpl::PrintCDATAText(const std::string & text) {
+  *pout_ << "<![CDATA[" << text << "]]>";
+}
+
+}
diff --git a/talk/xmllite/xmlprinter.h b/talk/xmllite/xmlprinter.h
new file mode 100644
index 0000000..96900d0
--- /dev/null
+++ b/talk/xmllite/xmlprinter.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 _xmlprinter_h_
+#define _xmlprinter_h_
+
+#include <iosfwd>
+#include <string>
+#include "talk/base/scoped_ptr.h"
+
+namespace buzz {
+
+class XmlElement;
+
+class XmlPrinter {
+public:
+  static void PrintXml(std::ostream * pout, const XmlElement * pelt);
+
+  static void PrintXml(std::ostream * pout, const XmlElement * pelt,
+    const std::string * const xmlns, int xmlnsCount);
+};
+
+}
+
+#endif
diff --git a/talk/xmpp/asyncsocket.h b/talk/xmpp/asyncsocket.h
new file mode 100644
index 0000000..e4bce7f
--- /dev/null
+++ b/talk/xmpp/asyncsocket.h
@@ -0,0 +1,86 @@
+/*
+ * 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 _ASYNCSOCKET_H_
+#define _ASYNCSOCKET_H_
+
+#include "talk/base/sigslot.h"
+
+namespace talk_base {
+  class SocketAddress;
+}
+
+namespace buzz {
+
+class AsyncSocket {
+public:
+  enum State {
+    STATE_CLOSED = 0,      //!< Socket is not open.
+    STATE_CLOSING,         //!< Socket is closing but can have buffered data
+    STATE_CONNECTING,      //!< In the process of
+    STATE_OPEN,            //!< Socket is connected
+#if defined(FEATURE_ENABLE_SSL)
+    STATE_TLS_CONNECTING,  //!< Establishing TLS connection
+    STATE_TLS_OPEN,        //!< TLS connected
+#endif
+  };
+
+  enum Error {
+    ERROR_NONE = 0,         //!< No error
+    ERROR_WINSOCK,          //!< Winsock error
+    ERROR_DNS,              //!< Couldn't resolve host name
+    ERROR_WRONGSTATE,       //!< Call made while socket is in the wrong state
+#if defined(FEATURE_ENABLE_SSL)
+    ERROR_SSL,              //!< Something went wrong with OpenSSL
+#endif
+  };
+
+  virtual ~AsyncSocket() {}
+  virtual State state() = 0;
+  virtual Error error() = 0;
+  virtual int GetError() = 0;    // winsock error code
+
+  virtual bool Connect(const talk_base::SocketAddress& addr) = 0;
+  virtual bool Read(char * data, size_t len, size_t* len_read) = 0;
+  virtual bool Write(const char * data, size_t len) = 0;
+  virtual bool Close() = 0;
+#if defined(FEATURE_ENABLE_SSL)
+  // We allow matching any passed domain.
+  // If both names are passed as empty, we do not require a match.
+  virtual bool StartTls(const std::string & domainname) = 0;
+#endif
+
+  sigslot::signal0<> SignalConnected;
+  sigslot::signal0<> SignalSSLConnected;
+  sigslot::signal0<> SignalClosed;
+  sigslot::signal0<> SignalRead;
+  sigslot::signal0<> SignalError;
+};
+
+}
+
+#endif
diff --git a/talk/xmpp/constants.cc b/talk/xmpp/constants.cc
new file mode 100644
index 0000000..aa581f4
--- /dev/null
+++ b/talk/xmpp/constants.cc
@@ -0,0 +1,467 @@
+/*
+ * 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 <string>
+#include "talk/base/basicdefs.h"
+#include "talk/xmllite/xmlconstants.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmpp/jid.h"
+#include "talk/xmpp/constants.h"
+namespace buzz {
+
+const Jid JID_EMPTY(STR_EMPTY);
+
+const std::string & Constants::ns_client() {
+  static const std::string ns_client_("jabber:client");
+  return ns_client_;
+}
+
+const std::string & Constants::ns_server() {
+  static const std::string ns_server_("jabber:server");
+  return ns_server_;
+}
+
+const std::string & Constants::ns_stream() {
+  static const std::string ns_stream_("http://etherx.jabber.org/streams");
+  return ns_stream_;
+}
+
+const std::string & Constants::ns_xstream() {
+  static const std::string ns_xstream_("urn:ietf:params:xml:ns:xmpp-streams");
+  return ns_xstream_;
+}
+
+const std::string & Constants::ns_tls() {
+  static const std::string ns_tls_("urn:ietf:params:xml:ns:xmpp-tls");
+  return ns_tls_;
+}
+
+const std::string & Constants::ns_sasl() {
+  static const std::string ns_sasl_("urn:ietf:params:xml:ns:xmpp-sasl");
+  return ns_sasl_;
+}
+
+const std::string & Constants::ns_bind() {
+  static const std::string ns_bind_("urn:ietf:params:xml:ns:xmpp-bind");
+  return ns_bind_;
+}
+
+const std::string & Constants::ns_dialback() {
+  static const std::string ns_dialback_("jabber:server:dialback");
+  return ns_dialback_;
+}
+
+const std::string & Constants::ns_session() {
+  static const std::string ns_session_("urn:ietf:params:xml:ns:xmpp-session");
+  return ns_session_;
+}
+
+const std::string & Constants::ns_stanza() {
+  static const std::string ns_stanza_("urn:ietf:params:xml:ns:xmpp-stanzas");
+  return ns_stanza_;
+}
+
+const std::string & Constants::ns_privacy() {
+  static const std::string ns_privacy_("jabber:iq:privacy");
+  return ns_privacy_;
+}
+
+const std::string & Constants::ns_roster() {
+  static const std::string ns_roster_("jabber:iq:roster");
+  return ns_roster_;
+}
+
+const std::string & Constants::ns_vcard() {
+  static const std::string ns_vcard_("vcard-temp");
+  return ns_vcard_;
+}
+
+const std::string & Constants::ns_avatar_hash() {
+  static const std::string ns_avatar_hash_("google:avatar");
+  return ns_avatar_hash_;
+}
+
+const std::string & Constants::ns_vcard_update() {
+  static const std::string ns_vcard_update_("vcard-temp:x:update");
+  return ns_vcard_update_;
+}
+
+const std::string & Constants::str_client() {
+  static const std::string str_client_("client");
+  return str_client_;
+}
+
+const std::string & Constants::str_server() {
+  static const std::string str_server_("server");
+  return str_server_;
+}
+
+const std::string & Constants::str_stream() {
+  static const std::string str_stream_("stream");
+  return str_stream_;
+}
+
+const std::string STR_GET("get");
+const std::string STR_SET("set");
+const std::string STR_RESULT("result");
+const std::string STR_ERROR("error");
+
+
+const std::string STR_FROM("from");
+const std::string STR_TO("to");
+const std::string STR_BOTH("both");
+const std::string STR_REMOVE("remove");
+
+const std::string STR_UNAVAILABLE("unavailable");
+
+const std::string STR_GOOGLE_COM("google.com");
+const std::string STR_GMAIL_COM("gmail.com");
+const std::string STR_GOOGLEMAIL_COM("googlemail.com");
+const std::string STR_DEFAULT_DOMAIN("default.talk.google.com");
+const std::string STR_TALK_GOOGLE_COM("talk.google.com");
+const std::string STR_TALKX_L_GOOGLE_COM("talkx.l.google.com");
+
+const std::string STR_X("x");
+
+#ifdef FEATURE_ENABLE_VOICEMAIL
+const std::string STR_VOICEMAIL("voicemail");
+const std::string STR_OUTGOINGVOICEMAIL("outgoingvoicemail");
+#endif
+
+const QName QN_STREAM_STREAM(true, NS_STREAM, STR_STREAM);
+const QName QN_STREAM_FEATURES(true, NS_STREAM, "features");
+const QName QN_STREAM_ERROR(true, NS_STREAM, "error");
+
+const QName QN_XSTREAM_BAD_FORMAT(true, NS_XSTREAM, "bad-format");
+const QName QN_XSTREAM_BAD_NAMESPACE_PREFIX(true, NS_XSTREAM, "bad-namespace-prefix");
+const QName QN_XSTREAM_CONFLICT(true, NS_XSTREAM, "conflict");
+const QName QN_XSTREAM_CONNECTION_TIMEOUT(true, NS_XSTREAM, "connection-timeout");
+const QName QN_XSTREAM_HOST_GONE(true, NS_XSTREAM, "host-gone");
+const QName QN_XSTREAM_HOST_UNKNOWN(true, NS_XSTREAM, "host-unknown");
+const QName QN_XSTREAM_IMPROPER_ADDRESSIING(true, NS_XSTREAM, "improper-addressing");
+const QName QN_XSTREAM_INTERNAL_SERVER_ERROR(true, NS_XSTREAM, "internal-server-error");
+const QName QN_XSTREAM_INVALID_FROM(true, NS_XSTREAM, "invalid-from");
+const QName QN_XSTREAM_INVALID_ID(true, NS_XSTREAM, "invalid-id");
+const QName QN_XSTREAM_INVALID_NAMESPACE(true, NS_XSTREAM, "invalid-namespace");
+const QName QN_XSTREAM_INVALID_XML(true, NS_XSTREAM, "invalid-xml");
+const QName QN_XSTREAM_NOT_AUTHORIZED(true, NS_XSTREAM, "not-authorized");
+const QName QN_XSTREAM_POLICY_VIOLATION(true, NS_XSTREAM, "policy-violation");
+const QName QN_XSTREAM_REMOTE_CONNECTION_FAILED(true, NS_XSTREAM, "remote-connection-failed");
+const QName QN_XSTREAM_RESOURCE_CONSTRAINT(true, NS_XSTREAM, "resource-constraint");
+const QName QN_XSTREAM_RESTRICTED_XML(true, NS_XSTREAM, "restricted-xml");
+const QName QN_XSTREAM_SEE_OTHER_HOST(true, NS_XSTREAM, "see-other-host");
+const QName QN_XSTREAM_SYSTEM_SHUTDOWN(true, NS_XSTREAM, "system-shutdown");
+const QName QN_XSTREAM_UNDEFINED_CONDITION(true, NS_XSTREAM, "undefined-condition");
+const QName QN_XSTREAM_UNSUPPORTED_ENCODING(true, NS_XSTREAM, "unsupported-encoding");
+const QName QN_XSTREAM_UNSUPPORTED_STANZA_TYPE(true, NS_XSTREAM, "unsupported-stanza-type");
+const QName QN_XSTREAM_UNSUPPORTED_VERSION(true, NS_XSTREAM, "unsupported-version");
+const QName QN_XSTREAM_XML_NOT_WELL_FORMED(true, NS_XSTREAM, "xml-not-well-formed");
+const QName QN_XSTREAM_TEXT(true, NS_XSTREAM, "text");
+
+const QName QN_TLS_STARTTLS(true, NS_TLS, "starttls");
+const QName QN_TLS_REQUIRED(true, NS_TLS, "required");
+const QName QN_TLS_PROCEED(true, NS_TLS, "proceed");
+const QName QN_TLS_FAILURE(true, NS_TLS, "failure");
+
+const QName QN_SASL_MECHANISMS(true, NS_SASL, "mechanisms");
+const QName QN_SASL_MECHANISM(true, NS_SASL, "mechanism");
+const QName QN_SASL_AUTH(true, NS_SASL, "auth");
+const QName QN_SASL_CHALLENGE(true, NS_SASL, "challenge");
+const QName QN_SASL_RESPONSE(true, NS_SASL, "response");
+const QName QN_SASL_ABORT(true, NS_SASL, "abort");
+const QName QN_SASL_SUCCESS(true, NS_SASL, "success");
+const QName QN_SASL_FAILURE(true, NS_SASL, "failure");
+const QName QN_SASL_ABORTED(true, NS_SASL, "aborted");
+const QName QN_SASL_INCORRECT_ENCODING(true, NS_SASL, "incorrect-encoding");
+const QName QN_SASL_INVALID_AUTHZID(true, NS_SASL, "invalid-authzid");
+const QName QN_SASL_INVALID_MECHANISM(true, NS_SASL, "invalid-mechanism");
+const QName QN_SASL_MECHANISM_TOO_WEAK(true, NS_SASL, "mechanism-too-weak");
+const QName QN_SASL_NOT_AUTHORIZED(true, NS_SASL, "not-authorized");
+const QName QN_SASL_TEMPORARY_AUTH_FAILURE(true, NS_SASL, "temporary-auth-failure");
+
+const QName QN_DIALBACK_RESULT(true, NS_DIALBACK, "result");
+const QName QN_DIALBACK_VERIFY(true, NS_DIALBACK, "verify");
+
+const QName QN_STANZA_BAD_REQUEST(true, NS_STANZA, "bad-request");
+const QName QN_STANZA_CONFLICT(true, NS_STANZA, "conflict");
+const QName QN_STANZA_FEATURE_NOT_IMPLEMENTED(true, NS_STANZA, "feature-not-implemented");
+const QName QN_STANZA_FORBIDDEN(true, NS_STANZA, "forbidden");
+const QName QN_STANZA_GONE(true, NS_STANZA, "gone");
+const QName QN_STANZA_INTERNAL_SERVER_ERROR(true, NS_STANZA, "internal-server-error");
+const QName QN_STANZA_ITEM_NOT_FOUND(true, NS_STANZA, "item-not-found");
+const QName QN_STANZA_JID_MALFORMED(true, NS_STANZA, "jid-malformed");
+const QName QN_STANZA_NOT_ACCEPTABLE(true, NS_STANZA, "not-acceptable");
+const QName QN_STANZA_NOT_ALLOWED(true, NS_STANZA, "not-allowed");
+const QName QN_STANZA_PAYMENT_REQUIRED(true, NS_STANZA, "payment-required");
+const QName QN_STANZA_RECIPIENT_UNAVAILABLE(true, NS_STANZA, "recipient-unavailable");
+const QName QN_STANZA_REDIRECT(true, NS_STANZA, "redirect");
+const QName QN_STANZA_REGISTRATION_REQUIRED(true, NS_STANZA, "registration-required");
+const QName QN_STANZA_REMOTE_SERVER_NOT_FOUND(true, NS_STANZA, "remote-server-not-found");
+const QName QN_STANZA_REMOTE_SERVER_TIMEOUT(true, NS_STANZA, "remote-server-timeout");
+const QName QN_STANZA_RESOURCE_CONSTRAINT(true, NS_STANZA, "resource-constraint");
+const QName QN_STANZA_SERVICE_UNAVAILABLE(true, NS_STANZA, "service-unavailable");
+const QName QN_STANZA_SUBSCRIPTION_REQUIRED(true, NS_STANZA, "subscription-required");
+const QName QN_STANZA_UNDEFINED_CONDITION(true, NS_STANZA, "undefined-condition");
+const QName QN_STANZA_UNEXPECTED_REQUEST(true, NS_STANZA, "unexpected-request");
+const QName QN_STANZA_TEXT(true, NS_STANZA, "text");
+
+const QName QN_BIND_BIND(true, NS_BIND, "bind");
+const QName QN_BIND_RESOURCE(true, NS_BIND, "resource");
+const QName QN_BIND_JID(true, NS_BIND, "jid");
+
+const QName QN_MESSAGE(true, NS_CLIENT, "message");
+const QName QN_BODY(true, NS_CLIENT, "body");
+const QName QN_SUBJECT(true, NS_CLIENT, "subject");
+const QName QN_THREAD(true, NS_CLIENT, "thread");
+const QName QN_PRESENCE(true, NS_CLIENT, "presence");
+const QName QN_SHOW(true, NS_CLIENT, "show");
+const QName QN_STATUS(true, NS_CLIENT, "status");
+const QName QN_LANG(true, NS_CLIENT, "lang");
+const QName QN_PRIORITY(true, NS_CLIENT, "priority");
+const QName QN_IQ(true, NS_CLIENT, "iq");
+const QName QN_ERROR(true, NS_CLIENT, "error");
+
+const QName QN_SERVER_MESSAGE(true, NS_SERVER, "message");
+const QName QN_SERVER_BODY(true, NS_SERVER, "body");
+const QName QN_SERVER_SUBJECT(true, NS_SERVER, "subject");
+const QName QN_SERVER_THREAD(true, NS_SERVER, "thread");
+const QName QN_SERVER_PRESENCE(true, NS_SERVER, "presence");
+const QName QN_SERVER_SHOW(true, NS_SERVER, "show");
+const QName QN_SERVER_STATUS(true, NS_SERVER, "status");
+const QName QN_SERVER_LANG(true, NS_SERVER, "lang");
+const QName QN_SERVER_PRIORITY(true, NS_SERVER, "priority");
+const QName QN_SERVER_IQ(true, NS_SERVER, "iq");
+const QName QN_SERVER_ERROR(true, NS_SERVER, "error");
+
+const QName QN_SESSION_SESSION(true, NS_SESSION, "session");
+
+const QName QN_PRIVACY_QUERY(true, NS_PRIVACY, "query");
+const QName QN_PRIVACY_ACTIVE(true, NS_PRIVACY, "active");
+const QName QN_PRIVACY_DEFAULT(true, NS_PRIVACY, "default");
+const QName QN_PRIVACY_LIST(true, NS_PRIVACY, "list");
+const QName QN_PRIVACY_ITEM(true, NS_PRIVACY, "item");
+const QName QN_PRIVACY_IQ(true, NS_PRIVACY, "iq");
+const QName QN_PRIVACY_MESSAGE(true, NS_PRIVACY, "message");
+const QName QN_PRIVACY_PRESENCE_IN(true, NS_PRIVACY, "presence-in");
+const QName QN_PRIVACY_PRESENCE_OUT(true, NS_PRIVACY, "presence-out");
+
+const QName QN_ROSTER_QUERY(true, NS_ROSTER, "query");
+const QName QN_ROSTER_ITEM(true, NS_ROSTER, "item");
+const QName QN_ROSTER_GROUP(true, NS_ROSTER, "group");
+
+const QName QN_VCARD(true, NS_VCARD, "vCard");
+const QName QN_VCARD_FN(true, NS_VCARD, "FN");
+const QName QN_VCARD_PHOTO(true, NS_VCARD, "PHOTO");
+const QName QN_VCARD_PHOTO_BINVAL(true, NS_VCARD, "BINVAL");
+const QName QN_VCARD_AVATAR_HASH(true, NS_AVATAR_HASH, "hash");
+const QName QN_VCARD_AVATAR_HASH_MODIFIED(true, NS_AVATAR_HASH, "modified");
+
+const buzz::QName QN_NAME(true, STR_EMPTY, "name");
+const buzz::QName QN_AFFILIATION(true, STR_EMPTY, "affiliation");
+const buzz::QName QN_ROLE(true, STR_EMPTY, "role");
+
+#if defined(FEATURE_ENABLE_PSTN)
+const QName QN_VCARD_TEL(true, NS_VCARD, "TEL");
+const QName QN_VCARD_VOICE(true, NS_VCARD, "VOICE");
+const QName QN_VCARD_HOME(true, NS_VCARD, "HOME");
+const QName QN_VCARD_WORK(true, NS_VCARD, "WORK");
+const QName QN_VCARD_CELL(true, NS_VCARD, "CELL");
+const QName QN_VCARD_NUMBER(true, NS_VCARD, "NUMBER");
+#endif
+
+const QName QN_XML_LANG(true, NS_XML, "lang");
+
+const std::string STR_TYPE("type");
+const std::string STR_ID("id");
+const std::string STR_NAME("name");
+const std::string STR_JID("jid");
+const std::string STR_SUBSCRIPTION("subscription");
+const std::string STR_ASK("ask");
+
+const QName QN_ENCODING(true, STR_EMPTY, STR_ENCODING);
+const QName QN_VERSION(true, STR_EMPTY, STR_VERSION);
+const QName QN_TO(true, STR_EMPTY, "to");
+const QName QN_FROM(true, STR_EMPTY, "from");
+const QName QN_TYPE(true, STR_EMPTY, "type");
+const QName QN_ID(true, STR_EMPTY, "id");
+const QName QN_CODE(true, STR_EMPTY, "code");
+
+const QName QN_VALUE(true, STR_EMPTY, "value");
+const QName QN_ACTION(true, STR_EMPTY, "action");
+const QName QN_ORDER(true, STR_EMPTY, "order");
+const QName QN_MECHANISM(true, STR_EMPTY, "mechanism");
+const QName QN_ASK(true, STR_EMPTY, "ask");
+const QName QN_JID(true, STR_EMPTY, "jid");
+const QName QN_SUBSCRIPTION(true, STR_EMPTY, "subscription");
+const QName QN_TITLE1(true, STR_EMPTY, "title1");
+const QName QN_TITLE2(true, STR_EMPTY, "title2");
+const QName QN_SOURCE(true, STR_EMPTY, "source");
+
+const QName QN_XMLNS_CLIENT(true, NS_XMLNS, STR_CLIENT);
+const QName QN_XMLNS_SERVER(true, NS_XMLNS, STR_SERVER);
+const QName QN_XMLNS_STREAM(true, NS_XMLNS, STR_STREAM);
+
+
+
+// Presence
+const std::string STR_SHOW_AWAY("away");
+const std::string STR_SHOW_CHAT("chat");
+const std::string STR_SHOW_DND("dnd");
+const std::string STR_SHOW_XA("xa");
+const std::string STR_SHOW_OFFLINE("offline");
+
+// Subscription
+const std::string STR_SUBSCRIBE("subscribe");
+const std::string STR_SUBSCRIBED("subscribed");
+const std::string STR_UNSUBSCRIBE("unsubscribe");
+const std::string STR_UNSUBSCRIBED("unsubscribed");
+
+// Google Invite
+const std::string NS_GOOGLE_INVITE("google:subscribe");
+const QName QN_INVITATION(true, NS_GOOGLE_INVITE, "invitation");
+const QName QN_INVITE_NAME(true, NS_GOOGLE_INVITE, "name");
+const QName QN_INVITE_SUBJECT(true, NS_GOOGLE_INVITE, "subject");
+const QName QN_INVITE_MESSAGE(true, NS_GOOGLE_INVITE, "body");
+
+// JEP 0030
+const QName QN_NODE(true, STR_EMPTY, "node");
+const QName QN_CATEGORY(true, STR_EMPTY, "category");
+const QName QN_VAR(true, STR_EMPTY, "var");
+const std::string NS_DISCO_INFO("http://jabber.org/protocol/disco#info");
+const std::string NS_DISCO_ITEMS("http://jabber.org/protocol/disco#items");
+const QName QN_DISCO_INFO_QUERY(true, NS_DISCO_INFO, "query");
+const QName QN_DISCO_IDENTITY(true, NS_DISCO_INFO, "identity");
+const QName QN_DISCO_FEATURE(true, NS_DISCO_INFO, "feature");
+
+const QName QN_DISCO_ITEMS_QUERY(true, NS_DISCO_ITEMS, "query");
+const QName QN_DISCO_ITEM(true, NS_DISCO_ITEMS, "item");
+
+const std::string NS_MUC_USER("http://jabber.org/protocol/muc#user");
+const QName QN_MUC_USER_CONTINUE(true, NS_MUC_USER, "continue");
+const QName QN_MUC_USER_X(true, NS_MUC_USER, "x");
+const QName QN_MUC_USER_ITEM(true, NS_MUC_USER, "item");
+const QName QN_MUC_USER_STATUS(true, NS_MUC_USER, "status");
+
+// JEP 0115
+const std::string NS_CAPS("http://jabber.org/protocol/caps");
+const QName QN_CAPS_C(true, NS_CAPS, "c");
+const QName QN_VER(true, STR_EMPTY, "ver");
+const QName QN_EXT(true, STR_EMPTY, "ext");
+
+// JEP 0153
+const std::string kNSVCard("vcard-temp:x:update");
+const QName kQnVCardX(true, kNSVCard, "x");
+const QName kQnVCardPhoto(true, kNSVCard, "photo");
+
+// JEP 0172 User Nickname
+const std::string kNSNickname("http://jabber.org/protocol/nick");
+const QName kQnNickname(true, kNSNickname, "nick");
+
+
+// JEP 0085 chat state
+const std::string NS_CHATSTATE("http://jabber.org/protocol/chatstates");
+const QName QN_CS_ACTIVE(true, NS_CHATSTATE, "active");
+const QName QN_CS_COMPOSING(true, NS_CHATSTATE, "composing");
+const QName QN_CS_PAUSED(true, NS_CHATSTATE, "paused");
+const QName QN_CS_INACTIVE(true, NS_CHATSTATE, "inactive");
+const QName QN_CS_GONE(true, NS_CHATSTATE, "gone");
+
+// JEP 0091 Delayed Delivery
+const std::string kNSDelay("jabber:x:delay");
+const QName kQnDelayX(true, kNSDelay, "x");
+const QName kQnStamp(true, STR_EMPTY, "stamp");
+
+// Google time stamping (higher resolution)
+const std::string kNSTimestamp("google:timestamp");
+const QName kQnTime(true, kNSTimestamp, "time");
+const QName kQnMilliseconds(true, STR_EMPTY, "ms");
+
+
+// Event tracking
+#ifdef FEATURE_ENABLE_TRACKING
+const std::string NS_GOOGLE_EVENT_TRACKING("google:client-usability-testing");
+const QName QN_EVENT_TRACKING(true, NS_GOOGLE_EVENT_TRACKING, "usage-stats");
+const QName QN_EVENT_TRACKING_BRANDID(true, NS_GOOGLE_EVENT_TRACKING, "bid");
+const QName QN_EVENT_TRACKING_EVENT(true, NS_GOOGLE_EVENT_TRACKING, "event");
+const QName QN_EVENT_TRACKING_VARIABLE_KEY(true, STR_EMPTY, "key");
+const QName QN_EVENT_TRACKING_VARIABLE_VALUE(true, STR_EMPTY, "value");
+const QName QN_EVENT_TRACKING_VARIABLE_TIME(true, STR_EMPTY, "time");
+const QName QN_EVENT_TRACKING_EVENT_GROUP(true,
+                                          NS_GOOGLE_EVENT_TRACKING, "events");
+#endif
+
+
+// Jingle Info
+const std::string NS_JINGLE_INFO("google:jingleinfo");
+const QName QN_JINGLE_INFO_QUERY(true, NS_JINGLE_INFO, "query");
+const QName QN_JINGLE_INFO_STUN(true, NS_JINGLE_INFO, "stun");
+const QName QN_JINGLE_INFO_RELAY(true, NS_JINGLE_INFO, "relay");
+const QName QN_JINGLE_INFO_SERVER(true, NS_JINGLE_INFO, "server");
+const QName QN_JINGLE_INFO_TOKEN(true, NS_JINGLE_INFO, "token");
+const QName QN_JINGLE_INFO_HOST(true, STR_EMPTY, "host");
+const QName QN_JINGLE_INFO_TCP(true, STR_EMPTY, "tcp");
+const QName QN_JINGLE_INFO_UDP(true, STR_EMPTY, "udp");
+const QName QN_JINGLE_INFO_TCPSSL(true, STR_EMPTY, "tcpssl");
+
+// Call Performance Logging
+const std::string NS_GOOGLE_CALLPERF_STATS("google:call-perf-stats");
+const QName QN_CALLPERF_STATS(true, NS_GOOGLE_CALLPERF_STATS, "callPerfStats");
+const QName QN_CALLPERF_SESSIONID(true, STR_EMPTY, "sessionId");
+const QName QN_CALLPERF_LOCALUSER(true, STR_EMPTY, "localUser");
+const QName QN_CALLPERF_REMOTEUSER(true, STR_EMPTY, "remoteUser");
+const QName QN_CALLPERF_STARTTIME(true, STR_EMPTY, "startTime");
+const QName QN_CALLPERF_CALL_LENGTH(true, STR_EMPTY, "callLength");
+const QName QN_CALLPERF_DATAPOINT(true, NS_GOOGLE_CALLPERF_STATS, "dataPoint");
+const QName QN_CALLPERF_DATAPOINT_TIME(true, STR_EMPTY, "timeStamp");
+const QName QN_CALLPERF_DATAPOINT_FRACTION_LOST(true, STR_EMPTY, "fraction_lost");
+const QName QN_CALLPERF_DATAPOINT_CUM_LOST(true, STR_EMPTY, "cum_lost");
+const QName QN_CALLPERF_DATAPOINT_EXT_MAX(true, STR_EMPTY, "ext_max");
+const QName QN_CALLPERF_DATAPOINT_JITTER(true, STR_EMPTY, "jitter");
+const QName QN_CALLPERF_DATAPOINT_RTT(true, STR_EMPTY, "RTT");
+const QName QN_CALLPERF_DATAPOINT_BYTES_R(true, STR_EMPTY, "bytesReceived");
+const QName QN_CALLPERF_DATAPOINT_PACKETS_R(true, STR_EMPTY, "packetsReceived");
+const QName QN_CALLPERF_DATAPOINT_BYTES_S(true, STR_EMPTY, "bytesSent");
+const QName QN_CALLPERF_DATAPOINT_PACKETS_S(true, STR_EMPTY, "packetsSent");
+const QName QN_CALLPERF_CONNECTION(true, NS_GOOGLE_CALLPERF_STATS, "connection");
+const QName QN_CALLPERF_CONNECTION_LOCAL_ADDRESS(true, STR_EMPTY, "localAddress");
+const QName QN_CALLPERF_CONNECTION_REMOTE_ADDRESS(true, STR_EMPTY, "remoteAddress");
+
+// Muc invites.
+const QName QN_MUC_USER_INVITE(true, NS_MUC_USER, "invite");
+
+// Multiway audio/video.
+const std::string NS_GOOGLE_MUC_USER("google:muc#user");
+const QName QN_GOOGLE_MUC_USER_AVAILABLE_MEDIA(true, NS_GOOGLE_MUC_USER, "available-media");
+const QName QN_GOOGLE_MUC_USER_ENTRY(true, NS_GOOGLE_MUC_USER, "entry");
+const QName QN_GOOGLE_MUC_USER_MEDIA(true, NS_GOOGLE_MUC_USER, "media");
+const QName QN_GOOGLE_MUC_USER_TYPE(true, NS_GOOGLE_MUC_USER, "type");
+const QName QN_GOOGLE_MUC_USER_SRC_ID(true, NS_GOOGLE_MUC_USER, "src-id");
+const QName QN_GOOGLE_MUC_USER_STATUS(true, NS_GOOGLE_MUC_USER, "status");
+const QName QN_LABEL(true, STR_EMPTY, "label");
+
+}
diff --git a/talk/xmpp/constants.h b/talk/xmpp/constants.h
new file mode 100644
index 0000000..1a626b4
--- /dev/null
+++ b/talk/xmpp/constants.h
@@ -0,0 +1,436 @@
+/*
+ * 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_XMPP_CONSTANTS_H_
+#define TALK_XMPP_CONSTANTS_H_
+
+#include <string>
+#include "talk/xmllite/qname.h"
+#include "talk/xmpp/jid.h"
+
+
+#define NS_CLIENT Constants::ns_client()
+#define NS_SERVER Constants::ns_server()
+#define NS_STREAM Constants::ns_stream()
+#define NS_XSTREAM Constants::ns_xstream()
+#define NS_TLS Constants::ns_tls()
+#define NS_SASL Constants::ns_sasl()
+#define NS_BIND Constants::ns_bind()
+#define NS_DIALBACK Constants::ns_dialback()
+#define NS_SESSION Constants::ns_session()
+#define NS_STANZA Constants::ns_stanza()
+#define NS_PRIVACY Constants::ns_privacy()
+#define NS_ROSTER Constants::ns_roster()
+#define NS_VCARD Constants::ns_vcard()
+#define NS_AVATAR_HASH Constants::ns_avatar_hash()
+#define NS_VCARD_UPDATE Constants::ns_vcard_update()
+#define STR_CLIENT Constants::str_client()
+#define STR_SERVER Constants::str_server()
+#define STR_STREAM Constants::str_stream()
+
+
+namespace buzz {
+
+extern const Jid JID_EMPTY;
+
+class Constants {
+ public:
+  static const std::string & ns_client();
+  static const std::string & ns_server();
+  static const std::string & ns_stream();
+  static const std::string & ns_xstream();
+  static const std::string & ns_tls();
+  static const std::string & ns_sasl();
+  static const std::string & ns_bind();
+  static const std::string & ns_dialback();
+  static const std::string & ns_session();
+  static const std::string & ns_stanza();
+  static const std::string & ns_privacy();
+  static const std::string & ns_roster();
+  static const std::string & ns_vcard();
+  static const std::string & ns_avatar_hash();
+  static const std::string & ns_vcard_update();
+
+  static const std::string & str_client();
+  static const std::string & str_server();
+  static const std::string & str_stream();
+};
+
+extern const std::string STR_GET;
+extern const std::string STR_SET;
+extern const std::string STR_RESULT;
+extern const std::string STR_ERROR;
+
+
+extern const std::string STR_FROM;
+extern const std::string STR_TO;
+extern const std::string STR_BOTH;
+extern const std::string STR_REMOVE;
+
+extern const std::string STR_MESSAGE;
+extern const std::string STR_BODY;
+extern const std::string STR_PRESENCE;
+extern const std::string STR_STATUS;
+extern const std::string STR_SHOW;
+extern const std::string STR_PRIOIRTY;
+extern const std::string STR_IQ;
+
+extern const std::string STR_TYPE;
+extern const std::string STR_NAME;
+extern const std::string STR_ID;
+extern const std::string STR_JID;
+extern const std::string STR_SUBSCRIPTION;
+extern const std::string STR_ASK;
+extern const std::string STR_X;
+extern const std::string STR_GOOGLE_COM;
+extern const std::string STR_GMAIL_COM;
+extern const std::string STR_GOOGLEMAIL_COM;
+extern const std::string STR_DEFAULT_DOMAIN;
+extern const std::string STR_TALK_GOOGLE_COM;
+extern const std::string STR_TALKX_L_GOOGLE_COM;
+
+#ifdef FEATURE_ENABLE_VOICEMAIL
+extern const std::string STR_VOICEMAIL;
+extern const std::string STR_OUTGOINGVOICEMAIL;
+#endif
+
+extern const std::string STR_UNAVAILABLE;
+
+extern const QName QN_STREAM_STREAM;
+extern const QName QN_STREAM_FEATURES;
+extern const QName QN_STREAM_ERROR;
+
+extern const QName QN_XSTREAM_BAD_FORMAT;
+extern const QName QN_XSTREAM_BAD_NAMESPACE_PREFIX;
+extern const QName QN_XSTREAM_CONFLICT;
+extern const QName QN_XSTREAM_CONNECTION_TIMEOUT;
+extern const QName QN_XSTREAM_HOST_GONE;
+extern const QName QN_XSTREAM_HOST_UNKNOWN;
+extern const QName QN_XSTREAM_IMPROPER_ADDRESSIING;
+extern const QName QN_XSTREAM_INTERNAL_SERVER_ERROR;
+extern const QName QN_XSTREAM_INVALID_FROM;
+extern const QName QN_XSTREAM_INVALID_ID;
+extern const QName QN_XSTREAM_INVALID_NAMESPACE;
+extern const QName QN_XSTREAM_INVALID_XML;
+extern const QName QN_XSTREAM_NOT_AUTHORIZED;
+extern const QName QN_XSTREAM_POLICY_VIOLATION;
+extern const QName QN_XSTREAM_REMOTE_CONNECTION_FAILED;
+extern const QName QN_XSTREAM_RESOURCE_CONSTRAINT;
+extern const QName QN_XSTREAM_RESTRICTED_XML;
+extern const QName QN_XSTREAM_SEE_OTHER_HOST;
+extern const QName QN_XSTREAM_SYSTEM_SHUTDOWN;
+extern const QName QN_XSTREAM_UNDEFINED_CONDITION;
+extern const QName QN_XSTREAM_UNSUPPORTED_ENCODING;
+extern const QName QN_XSTREAM_UNSUPPORTED_STANZA_TYPE;
+extern const QName QN_XSTREAM_UNSUPPORTED_VERSION;
+extern const QName QN_XSTREAM_XML_NOT_WELL_FORMED;
+extern const QName QN_XSTREAM_TEXT;
+
+extern const QName QN_TLS_STARTTLS;
+extern const QName QN_TLS_REQUIRED;
+extern const QName QN_TLS_PROCEED;
+extern const QName QN_TLS_FAILURE;
+
+extern const QName QN_SASL_MECHANISMS;
+extern const QName QN_SASL_MECHANISM;
+extern const QName QN_SASL_AUTH;
+extern const QName QN_SASL_CHALLENGE;
+extern const QName QN_SASL_RESPONSE;
+extern const QName QN_SASL_ABORT;
+extern const QName QN_SASL_SUCCESS;
+extern const QName QN_SASL_FAILURE;
+extern const QName QN_SASL_ABORTED;
+extern const QName QN_SASL_INCORRECT_ENCODING;
+extern const QName QN_SASL_INVALID_AUTHZID;
+extern const QName QN_SASL_INVALID_MECHANISM;
+extern const QName QN_SASL_MECHANISM_TOO_WEAK;
+extern const QName QN_SASL_NOT_AUTHORIZED;
+extern const QName QN_SASL_TEMPORARY_AUTH_FAILURE;
+
+extern const QName QN_DIALBACK_RESULT;
+extern const QName QN_DIALBACK_VERIFY;
+
+extern const QName QN_STANZA_BAD_REQUEST;
+extern const QName QN_STANZA_CONFLICT;
+extern const QName QN_STANZA_FEATURE_NOT_IMPLEMENTED;
+extern const QName QN_STANZA_FORBIDDEN;
+extern const QName QN_STANZA_GONE;
+extern const QName QN_STANZA_INTERNAL_SERVER_ERROR;
+extern const QName QN_STANZA_ITEM_NOT_FOUND;
+extern const QName QN_STANZA_JID_MALFORMED;
+extern const QName QN_STANZA_NOT_ACCEPTABLE;
+extern const QName QN_STANZA_NOT_ALLOWED;
+extern const QName QN_STANZA_PAYMENT_REQUIRED;
+extern const QName QN_STANZA_RECIPIENT_UNAVAILABLE;
+extern const QName QN_STANZA_REDIRECT;
+extern const QName QN_STANZA_REGISTRATION_REQUIRED;
+extern const QName QN_STANZA_REMOTE_SERVER_NOT_FOUND;
+extern const QName QN_STANZA_REMOTE_SERVER_TIMEOUT;
+extern const QName QN_STANZA_RESOURCE_CONSTRAINT;
+extern const QName QN_STANZA_SERVICE_UNAVAILABLE;
+extern const QName QN_STANZA_SUBSCRIPTION_REQUIRED;
+extern const QName QN_STANZA_UNDEFINED_CONDITION;
+extern const QName QN_STANZA_UNEXPECTED_REQUEST;
+extern const QName QN_STANZA_TEXT;
+
+extern const QName QN_BIND_BIND;
+extern const QName QN_BIND_RESOURCE;
+extern const QName QN_BIND_JID;
+
+extern const QName QN_MESSAGE;
+extern const QName QN_BODY;
+extern const QName QN_SUBJECT;
+extern const QName QN_THREAD;
+extern const QName QN_PRESENCE;
+extern const QName QN_SHOW;
+extern const QName QN_STATUS;
+extern const QName QN_LANG;
+extern const QName QN_PRIORITY;
+extern const QName QN_IQ;
+extern const QName QN_ERROR;
+
+extern const QName QN_SERVER_MESSAGE;
+extern const QName QN_SERVER_BODY;
+extern const QName QN_SERVER_SUBJECT;
+extern const QName QN_SERVER_THREAD;
+extern const QName QN_SERVER_PRESENCE;
+extern const QName QN_SERVER_SHOW;
+extern const QName QN_SERVER_STATUS;
+extern const QName QN_SERVER_LANG;
+extern const QName QN_SERVER_PRIORITY;
+extern const QName QN_SERVER_IQ;
+extern const QName QN_SERVER_ERROR;
+
+extern const QName QN_SESSION_SESSION;
+
+extern const QName QN_PRIVACY_QUERY;
+extern const QName QN_PRIVACY_ACTIVE;
+extern const QName QN_PRIVACY_DEFAULT;
+extern const QName QN_PRIVACY_LIST;
+extern const QName QN_PRIVACY_ITEM;
+extern const QName QN_PRIVACY_IQ;
+extern const QName QN_PRIVACY_MESSAGE;
+extern const QName QN_PRIVACY_PRESENCE_IN;
+extern const QName QN_PRIVACY_PRESENCE_OUT;
+
+extern const QName QN_ROSTER_QUERY;
+extern const QName QN_ROSTER_ITEM;
+extern const QName QN_ROSTER_GROUP;
+
+extern const QName QN_VCARD;
+extern const QName QN_VCARD_FN;
+extern const QName QN_VCARD_PHOTO;
+extern const QName QN_VCARD_PHOTO_BINVAL;
+extern const QName QN_VCARD_AVATAR_HASH;
+extern const QName QN_VCARD_AVATAR_HASH_MODIFIED;
+
+#if defined(FEATURE_ENABLE_PSTN)
+extern const QName QN_VCARD_TEL;
+extern const QName QN_VCARD_VOICE;
+extern const QName QN_VCARD_HOME;
+extern const QName QN_VCARD_WORK;
+extern const QName QN_VCARD_CELL;
+extern const QName QN_VCARD_NUMBER;
+#endif
+
+#if defined(FEATURE_ENABLE_RICHPROFILES)
+extern const QName QN_USER_PROFILE_QUERY;
+extern const QName QN_USER_PROFILE_URL;
+
+extern const QName QN_ATOM_FEED;
+extern const QName QN_ATOM_ENTRY;
+extern const QName QN_ATOM_TITLE;
+extern const QName QN_ATOM_ID;
+extern const QName QN_ATOM_MODIFIED;
+extern const QName QN_ATOM_IMAGE;
+extern const QName QN_ATOM_LINK;
+extern const QName QN_ATOM_HREF;
+#endif
+
+extern const QName QN_XML_LANG;
+
+extern const QName QN_ENCODING;
+extern const QName QN_VERSION;
+extern const QName QN_TO;
+extern const QName QN_FROM;
+extern const QName QN_TYPE;
+extern const QName QN_ID;
+extern const QName QN_CODE;
+extern const QName QN_NAME;
+extern const QName QN_VALUE;
+extern const QName QN_ACTION;
+extern const QName QN_ORDER;
+extern const QName QN_MECHANISM;
+extern const QName QN_ASK;
+extern const QName QN_JID;
+extern const QName QN_SUBSCRIPTION;
+extern const QName QN_TITLE1;
+extern const QName QN_TITLE2;
+extern const QName QN_AFFILIATION;
+extern const QName QN_ROLE;
+
+
+extern const QName QN_XMLNS_CLIENT;
+extern const QName QN_XMLNS_SERVER;
+extern const QName QN_XMLNS_STREAM;
+
+// Presence
+extern const std::string STR_SHOW_AWAY;
+extern const std::string STR_SHOW_CHAT;
+extern const std::string STR_SHOW_DND;
+extern const std::string STR_SHOW_XA;
+extern const std::string STR_SHOW_OFFLINE;
+
+// Subscription
+extern const std::string STR_SUBSCRIBE;
+extern const std::string STR_SUBSCRIBED;
+extern const std::string STR_UNSUBSCRIBE;
+extern const std::string STR_UNSUBSCRIBED;
+
+// Google Invite
+extern const std::string NS_GOOGLE_SUBSCRIBE;
+extern const QName QN_INVITATION;
+extern const QName QN_INVITE_NAME;
+extern const QName QN_INVITE_SUBJECT;
+extern const QName QN_INVITE_MESSAGE;
+
+
+// JEP 0030
+extern const QName QN_NODE;
+extern const QName QN_CATEGORY;
+extern const QName QN_VAR;
+extern const std::string NS_DISCO_INFO;
+extern const std::string NS_DISCO_ITEMS;
+
+extern const QName QN_DISCO_INFO_QUERY;
+extern const QName QN_DISCO_IDENTITY;
+extern const QName QN_DISCO_FEATURE;
+
+extern const QName QN_DISCO_ITEMS_QUERY;
+extern const QName QN_DISCO_ITEM;
+
+
+// JEP 0045
+extern const std::string NS_MUC;
+extern const QName QN_MUC_X;
+extern const QName QN_MUC_ITEM;
+extern const QName QN_MUC_AFFILIATION;
+extern const QName QN_MUC_ROLE;
+extern const std::string STR_AFFILIATION_NONE;
+extern const std::string STR_ROLE_PARTICIPANT;
+extern const std::string NS_MUC_USER;
+extern const QName QN_MUC_USER_CONTINUE;
+extern const QName QN_MUC_USER_X;
+extern const QName QN_MUC_USER_ITEM;
+extern const QName QN_MUC_USER_STATUS;
+
+
+// JEP 0115
+extern const std::string NS_CAPS;
+extern const QName QN_CAPS_C;
+extern const QName QN_VER;
+extern const QName QN_EXT;
+
+
+// Avatar - JEP 0153
+extern const std::string kNSVCard;
+extern const QName kQnVCardX;
+extern const QName kQnVCardPhoto;
+
+// JEP 0172 User Nickname
+extern const std::string kNSNickname;
+extern const QName kQnNickname;
+
+
+// JEP 0085 chat state
+extern const std::string NS_CHATSTATE;
+extern const QName QN_CS_ACTIVE;
+extern const QName QN_CS_COMPOSING;
+extern const QName QN_CS_PAUSED;
+extern const QName QN_CS_INACTIVE;
+extern const QName QN_CS_GONE;
+
+// JEP 0091 Delayed Delivery
+extern const std::string kNSDelay;
+extern const QName kQnDelayX;
+extern const QName kQnStamp;
+
+// Google time stamping (higher resolution)
+extern const std::string kNSTimestamp;
+extern const QName kQnTime;
+extern const QName kQnMilliseconds;
+
+
+extern const std::string NS_JINGLE_INFO;
+extern const QName QN_JINGLE_INFO_QUERY;
+extern const QName QN_JINGLE_INFO_STUN;
+extern const QName QN_JINGLE_INFO_RELAY;
+extern const QName QN_JINGLE_INFO_SERVER;
+extern const QName QN_JINGLE_INFO_TOKEN;
+extern const QName QN_JINGLE_INFO_HOST;
+extern const QName QN_JINGLE_INFO_TCP;
+extern const QName QN_JINGLE_INFO_UDP;
+extern const QName QN_JINGLE_INFO_TCPSSL;
+
+extern const std::string NS_GOOGLE_CALLPERF_STATS;
+extern const QName QN_CALLPERF_STATS;
+extern const QName QN_CALLPERF_SESSIONID;
+extern const QName QN_CALLPERF_LOCALUSER;
+extern const QName QN_CALLPERF_REMOTEUSER;
+extern const QName QN_CALLPERF_STARTTIME;
+extern const QName QN_CALLPERF_CALL_LENGTH;
+extern const QName QN_CALLPERF_DATAPOINT;
+extern const QName QN_CALLPERF_DATAPOINT_TIME;
+extern const QName QN_CALLPERF_DATAPOINT_FRACTION_LOST;
+extern const QName QN_CALLPERF_DATAPOINT_CUM_LOST;
+extern const QName QN_CALLPERF_DATAPOINT_EXT_MAX;
+extern const QName QN_CALLPERF_DATAPOINT_JITTER;
+extern const QName QN_CALLPERF_DATAPOINT_RTT;
+extern const QName QN_CALLPERF_DATAPOINT_BYTES_R;
+extern const QName QN_CALLPERF_DATAPOINT_PACKETS_R;
+extern const QName QN_CALLPERF_DATAPOINT_BYTES_S;
+extern const QName QN_CALLPERF_DATAPOINT_PACKETS_S;
+extern const QName QN_CALLPERF_CONNECTION;
+extern const QName QN_CALLPERF_CONNECTION_LOCAL_ADDRESS;
+extern const QName QN_CALLPERF_CONNECTION_REMOTE_ADDRESS;
+
+// Muc invites.
+extern const QName QN_MUC_USER_INVITE;
+
+// Multiway audio/video.
+extern const std::string NS_GOOGLE_MUC_USER;
+extern const QName QN_GOOGLE_MUC_USER_AVAILABLE_MEDIA;
+extern const QName QN_GOOGLE_MUC_USER_ENTRY;
+extern const QName QN_GOOGLE_MUC_USER_MEDIA;
+extern const QName QN_GOOGLE_MUC_USER_TYPE;
+extern const QName QN_GOOGLE_MUC_USER_SRC_ID;
+extern const QName QN_GOOGLE_MUC_USER_STATUS;
+extern const QName QN_LABEL;
+
+}  // namespace buzz
+
+#endif  // TALK_XMPP_CONSTANTS_H_
diff --git a/talk/xmpp/jid.cc b/talk/xmpp/jid.cc
new file mode 100644
index 0000000..01a025f
--- /dev/null
+++ b/talk/xmpp/jid.cc
@@ -0,0 +1,503 @@
+/*
+ * 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/xmpp/jid.h"
+
+#include <ctype.h>
+
+#include <algorithm>
+#include <string>
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/xmpp/constants.h"
+
+namespace buzz {
+
+Jid::Jid() : data_(NULL) {
+}
+
+Jid::Jid(bool is_special, const std::string & special) {
+  data_ = is_special ? new Data(special, STR_EMPTY, STR_EMPTY) : NULL;
+}
+
+Jid::Jid(const std::string & jid_string) {
+  if (jid_string == STR_EMPTY) {
+    data_ = NULL;
+    return;
+  }
+
+  // First find the slash and slice of that part
+  size_t slash = jid_string.find('/');
+  std::string resource_name = (slash == std::string::npos ? STR_EMPTY :
+                    jid_string.substr(slash + 1));
+
+  // Now look for the node
+  std::string node_name;
+  size_t at = jid_string.find('@');
+  size_t domain_begin;
+  if (at < slash && at != std::string::npos) {
+    node_name = jid_string.substr(0, at);
+    domain_begin = at + 1;
+  } else {
+    domain_begin = 0;
+  }
+
+  // Now take what is left as the domain
+  size_t domain_length =
+    (  slash == std::string::npos
+     ? jid_string.length() - domain_begin
+     : slash - domain_begin);
+
+  // avoid allocating these constants repeatedly
+  std::string domain_name;
+
+  if (domain_length == 9  && jid_string.find("gmail.com", domain_begin) == domain_begin) {
+    domain_name = STR_GMAIL_COM;
+  }
+  else if (domain_length == 14 && jid_string.find("googlemail.com", domain_begin) == domain_begin) {
+    domain_name = STR_GOOGLEMAIL_COM;
+  }
+  else if (domain_length == 10 && jid_string.find("google.com", domain_begin) == domain_begin) {
+    domain_name = STR_GOOGLE_COM;
+  }
+  else {
+    domain_name = jid_string.substr(domain_begin, domain_length);
+  }
+
+  // If the domain is empty we have a non-valid jid and we should empty
+  // everything else out
+  if (domain_name.empty()) {
+    data_ = NULL;
+    return;
+  }
+  
+  bool valid_node;
+  std::string validated_node = prepNode(node_name, 
+      node_name.begin(), node_name.end(), &valid_node);
+  bool valid_domain;
+  std::string validated_domain = prepDomain(domain_name,
+      domain_name.begin(), domain_name.end(), &valid_domain);
+  bool valid_resource;
+  std::string validated_resource = prepResource(resource_name,
+      resource_name.begin(), resource_name.end(), &valid_resource);
+
+  if (!valid_node || !valid_domain || !valid_resource) {
+    data_ = NULL;
+    return;
+  }
+
+  data_ = new Data(validated_node, validated_domain, validated_resource);
+}
+
+Jid::Jid(const std::string & node_name,
+         const std::string & domain_name,
+         const std::string & resource_name) {
+  if (domain_name.empty()) {
+    data_ = NULL;
+    return;
+  }
+
+  bool valid_node;
+  std::string validated_node = prepNode(node_name, 
+      node_name.begin(), node_name.end(), &valid_node);
+  bool valid_domain;
+  std::string validated_domain = prepDomain(domain_name,
+      domain_name.begin(), domain_name.end(), &valid_domain);
+  bool valid_resource;
+  std::string validated_resource = prepResource(resource_name,
+      resource_name.begin(), resource_name.end(), &valid_resource);
+
+  if (!valid_node || !valid_domain || !valid_resource) {
+    data_ = NULL;
+    return;
+  }
+
+  data_ = new Data(validated_node, validated_domain, validated_resource);
+}
+
+std::string Jid::Str() const {
+  if (!IsValid())
+    return STR_EMPTY;
+
+  std::string ret;
+
+  if (!data_->node_name_.empty())
+    ret = data_->node_name_ + "@";
+
+  ASSERT(data_->domain_name_ != STR_EMPTY);
+  ret += data_->domain_name_;
+
+  if (!data_->resource_name_.empty())
+    ret += "/" + data_->resource_name_;
+
+  return ret;
+}
+
+bool
+Jid::IsValid() const {
+  return data_ != NULL && !data_->domain_name_.empty();
+}
+
+bool
+Jid::IsBare() const {
+  if (Compare(JID_EMPTY) == 0) {
+    LOG(LS_VERBOSE) << "Warning: Calling IsBare() on the empty jid";
+    return true;
+  }
+  return IsValid() &&
+         data_->resource_name_.empty();
+}
+
+bool
+Jid::IsFull() const {
+  return IsValid() &&
+         !data_->resource_name_.empty();
+}
+
+Jid
+Jid::BareJid() const {
+  if (!IsValid())
+    return Jid();
+  if (!IsFull())
+    return *this;
+  return Jid(data_->node_name_, data_->domain_name_, STR_EMPTY);
+}
+
+#if 0
+void
+Jid::set_node(const std::string & node_name) {
+    data_->node_name_ = node_name;
+}
+void
+Jid::set_domain(const std::string & domain_name) {
+    data_->domain_name_ = domain_name;
+}
+void
+Jid::set_resource(const std::string & res_name) {
+    data_->resource_name_ = res_name;
+}
+#endif
+
+bool
+Jid::BareEquals(const Jid & other) const {
+  return (other.data_ == data_ ||
+          (data_ != NULL &&
+          other.data_ != NULL &&
+          other.data_->node_name_ == data_->node_name_ &&
+          other.data_->domain_name_ == data_->domain_name_));
+}
+
+bool
+Jid::operator==(const Jid & other) const {
+  return (other.data_ == data_ ||
+          (data_ != NULL &&
+          other.data_ != NULL &&
+          other.data_->node_name_ == data_->node_name_ &&
+          other.data_->domain_name_ == data_->domain_name_ &&
+          other.data_->resource_name_ == data_->resource_name_));
+}
+
+int
+Jid::Compare(const Jid & other) const {
+  if (other.data_ == data_)
+    return 0;
+  if (data_ == NULL)
+    return -1;
+  if (other.data_ == NULL)
+    return 1;
+  
+  int compare_result;
+  compare_result = data_->node_name_.compare(other.data_->node_name_);
+  if (0 != compare_result)
+    return compare_result;
+  compare_result = data_->domain_name_.compare(other.data_->domain_name_);
+  if (0 != compare_result)
+    return compare_result;
+  compare_result = data_->resource_name_.compare(other.data_->resource_name_);
+  return compare_result;
+}
+
+uint32 Jid::ComputeLameHash() const {
+  uint32 hash = 0;
+  // Hash the node portion
+  {
+    const std::string &str = node();
+    for (int i = 0; i < static_cast<int>(str.size()); ++i) {
+      hash = ((hash << 2) + hash) + str[i];
+    }
+  }
+
+  // Hash the domain portion
+  {
+    const std::string &str = domain();
+    for (int i = 0; i < static_cast<int>(str.size()); ++i)
+      hash = ((hash << 2) + hash) + str[i];
+  }
+
+  // Hash the resource portion
+  {
+    const std::string &str = resource();
+    for (int i = 0; i < static_cast<int>(str.size()); ++i)
+      hash = ((hash << 2) + hash) + str[i];
+  }
+
+  return hash;
+}
+
+// --- JID parsing code: ---
+
+// Checks and normalizes the node part of a JID.
+std::string
+Jid::prepNode(const std::string str, std::string::const_iterator start, 
+    std::string::const_iterator end, bool *valid) {
+  *valid = false;
+  std::string result;
+
+  for (std::string::const_iterator i = start; i < end; i++) {
+    bool char_valid = true;
+    unsigned char ch = *i;
+    if (ch <= 0x7F) {
+      result += prepNodeAscii(ch, &char_valid);
+    }
+    else {
+      // TODO: implement the correct stringprep protocol for these
+      result += tolower(ch);
+    }
+    if (!char_valid) {
+      return STR_EMPTY;
+    }
+  }
+
+  if (result.length() > 1023) {
+    return STR_EMPTY;
+  }
+  *valid = true;
+  return result;
+}
+
+
+// Returns the appropriate mapping for an ASCII character in a node.
+char
+Jid::prepNodeAscii(char ch, bool *valid) {
+  *valid = true;
+  switch (ch) {
+    case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
+    case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N':
+    case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U':
+    case 'V': case 'W': case 'X': case 'Y': case 'Z':
+      return (char)(ch + ('a' - 'A'));
+
+    case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05:
+    case 0x06: case 0x07: case 0x08: case 0x09: case 0x0A: case 0x0B:
+    case 0x0C: case 0x0D: case 0x0E: case 0x0F: case 0x10: case 0x11:
+    case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17:
+    case ' ': case '&': case '/': case ':': case '<': case '>': case '@':
+    case '\"': case '\'':
+    case 0x7F:
+      *valid = false;
+      return 0;
+
+    default:
+      return ch;
+  }
+}
+
+
+// Checks and normalizes the resource part of a JID.
+std::string
+Jid::prepResource(const std::string str, std::string::const_iterator start, 
+    std::string::const_iterator end, bool *valid) {
+  *valid = false;
+  std::string result;
+
+  for (std::string::const_iterator i = start; i < end; i++) {
+    bool char_valid = true;
+    unsigned char ch = *i;
+    if (ch <= 0x7F) {
+      result += prepResourceAscii(ch, &char_valid);
+    }
+    else {
+      // TODO: implement the correct stringprep protocol for these
+      result += ch;
+    }
+  }
+
+  if (result.length() > 1023) {
+    return STR_EMPTY;
+  }
+  *valid = true;
+  return result;
+}
+
+// Returns the appropriate mapping for an ASCII character in a resource.
+char
+Jid::prepResourceAscii(char ch, bool *valid) {
+  *valid = true;
+  switch (ch) {
+    case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05:
+    case 0x06: case 0x07: case 0x08: case 0x09: case 0x0A: case 0x0B:
+    case 0x0C: case 0x0D: case 0x0E: case 0x0F: case 0x10: case 0x11:
+    case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17:
+    case 0x7F:
+      *valid = false;
+      return 0;
+
+    default:
+      return ch;
+  }
+}
+
+// Checks and normalizes the domain part of a JID.
+std::string 
+Jid::prepDomain(const std::string str, std::string::const_iterator start, 
+    std::string::const_iterator end, bool *valid) {
+  *valid = false;
+  std::string result;
+
+  // TODO: if the domain contains a ':', then we should parse it
+  // as an IPv6 address rather than giving an error about illegal domain.
+  prepDomain(str, start, end, &result, valid);
+  if (!*valid) {
+    return STR_EMPTY;
+  }
+
+  if (result.length() > 1023) {
+    return STR_EMPTY;
+  }
+  *valid = true;
+  return result;
+}
+
+
+// Checks and normalizes an IDNA domain.
+void
+Jid::prepDomain(const std::string str, std::string::const_iterator start, 
+    std::string::const_iterator end, std::string *buf, bool *valid) {
+  *valid = false;
+  std::string::const_iterator last = start;
+  for (std::string::const_iterator i = start; i < end; i++) {
+    bool label_valid = true;
+    char ch = *i;
+    switch (ch) {
+      case 0x002E:
+#if 0 // FIX: This isn't UTF-8-aware.
+      case 0x3002:
+      case 0xFF0E:
+      case 0xFF61:
+#endif
+        prepDomainLabel(str, last, i, buf, &label_valid);
+        *buf += '.';
+        last = i + 1;
+        break;
+    }
+    if (!label_valid) {
+      return;
+    }
+  }
+  prepDomainLabel(str, last, end, buf, valid);
+}
+
+// Checks and normalizes a domain label.
+void
+Jid::prepDomainLabel(const std::string str, std::string::const_iterator start, 
+    std::string::const_iterator end, std::string *buf, bool *valid) {
+  *valid = false;
+
+  int startLen = buf->length();
+  for (std::string::const_iterator i = start; i < end; i++) {
+    bool char_valid = true;
+    unsigned char ch = *i;
+    if (ch <= 0x7F) {
+      *buf += prepDomainLabelAscii(ch, &char_valid);
+    }
+    else {
+      // TODO: implement ToASCII for these
+      *buf += ch;
+    }
+    if (!char_valid) {
+      return;
+    }
+  }
+
+  int count = buf->length() - startLen;
+  if (count == 0) {
+    return;
+  }
+  else if (count > 63) {
+    return;
+  }
+
+  // Is this check needed? See comment in prepDomainLabelAscii.
+  if ((*buf)[startLen] == '-') {
+    return;
+  }
+  if ((*buf)[buf->length() - 1] == '-') {
+    return;
+  }
+  *valid = true;
+}
+
+
+// Returns the appropriate mapping for an ASCII character in a domain label.
+char
+Jid::prepDomainLabelAscii(char ch, bool *valid) {
+  *valid = true;
+  // TODO: A literal reading of the spec seems to say that we do
+  // not need to check for these illegal characters (an "internationalized
+  // domain label" runs ToASCII with UseSTD3... set to false).  But that
+  // can't be right.  We should at least be checking that there are no '/'
+  // or '@' characters in the domain.  Perhaps we should see what others
+  // do in this case.
+
+  switch (ch) {
+    case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
+    case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N':
+    case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U':
+    case 'V': case 'W': case 'X': case 'Y': case 'Z':
+      return (char)(ch + ('a' - 'A'));
+
+    case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05:
+    case 0x06: case 0x07: case 0x08: case 0x09: case 0x0A: case 0x0B:
+    case 0x0C: case 0x0D: case 0x0E: case 0x0F: case 0x10: case 0x11:
+    case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17:
+    case 0x18: case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D:
+    case 0x1E: case 0x1F: case 0x20: case 0x21: case 0x22: case 0x23:
+    case 0x24: case 0x25: case 0x26: case 0x27: case 0x28: case 0x29:
+    case 0x2A: case 0x2B: case 0x2C: case 0x2E: case 0x2F: case 0x3A:
+    case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F: case 0x40:
+    case 0x5B: case 0x5C: case 0x5D: case 0x5E: case 0x5F: case 0x60:
+    case 0x7B: case 0x7C: case 0x7D: case 0x7E: case 0x7F:
+      *valid = false;
+      return 0;
+
+    default:
+      return ch;
+  }
+}
+
+}
diff --git a/talk/xmpp/jid.h b/talk/xmpp/jid.h
new file mode 100644
index 0000000..6831bda
--- /dev/null
+++ b/talk/xmpp/jid.h
@@ -0,0 +1,148 @@
+/*
+ * 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 _jid_h_
+#define _jid_h_
+
+#include <string>
+#include "talk/base/basictypes.h"
+#include "talk/xmllite/xmlconstants.h"
+
+namespace buzz {
+
+//! The Jid class encapsulates and provides parsing help for Jids
+//! A Jid consists of three parts. The node, the domain and the resource.
+//!
+//! node@domain/resource
+//!
+//! The node and resource are both optional.  A valid jid is defined to have
+//! a domain.  A bare jid is defined to not have a resource and a full jid
+//! *does* have a resource.
+class Jid {
+public:
+  explicit Jid();
+  explicit Jid(const std::string & jid_string);
+  explicit Jid(const std::string & node_name,
+               const std::string & domain_name,
+               const std::string & resource_name);
+  explicit Jid(bool special, const std::string & special_string);
+  Jid(const Jid & jid) : data_(jid.data_) {
+    if (data_ != NULL) {
+      data_->AddRef();
+    }
+  }
+  Jid & operator=(const Jid & jid) {
+    if (jid.data_ != NULL) {
+      jid.data_->AddRef();
+    }
+    if (data_ != NULL) {
+      data_->Release();
+    }
+    data_ = jid.data_;
+    return *this;
+  }
+  ~Jid() {
+    if (data_ != NULL) {
+      data_->Release();
+    }
+  }
+  
+
+  const std::string & node() const { return !data_ ? STR_EMPTY : data_->node_name_; }
+  // void set_node(const std::string & node_name);
+  const std::string & domain() const { return !data_ ? STR_EMPTY : data_->domain_name_; }
+  // void set_domain(const std::string & domain_name);
+  const std::string & resource() const { return !data_ ? STR_EMPTY : data_->resource_name_; }
+  // void set_resource(const std::string & res_name);
+
+  std::string Str() const;
+  Jid BareJid() const;
+
+  bool IsValid() const;
+  bool IsBare() const;
+  bool IsFull() const;
+
+  bool BareEquals(const Jid & other) const;
+
+  bool operator==(const Jid & other) const;
+  bool operator!=(const Jid & other) const { return !operator==(other); }
+
+  bool operator<(const Jid & other) const { return Compare(other) < 0; };
+  bool operator>(const Jid & other) const { return Compare(other) > 0; };
+  
+  int Compare(const Jid & other) const;
+
+  // A quick and dirty hash.  Don't count on this producing a great 
+  // distribution.
+  uint32 ComputeLameHash() const;
+
+private:
+
+  static std::string prepNode(const std::string str, 
+      std::string::const_iterator start, std::string::const_iterator end, 
+      bool *valid);
+  static char prepNodeAscii(char ch, bool *valid);
+  static std::string prepResource(const std::string str, 
+      std::string::const_iterator start, std::string::const_iterator end, 
+      bool *valid);
+  static char prepResourceAscii(char ch, bool *valid);
+  static std::string prepDomain(const std::string str, 
+      std::string::const_iterator start,  std::string::const_iterator end, 
+      bool *valid);
+  static void prepDomain(const std::string str, 
+      std::string::const_iterator start, std::string::const_iterator end, 
+      std::string *buf, bool *valid);
+  static void prepDomainLabel(const std::string str, 
+      std::string::const_iterator start, std::string::const_iterator end, 
+      std::string *buf, bool *valid);
+  static char prepDomainLabelAscii(char ch, bool *valid);
+
+  class Data {
+  public:
+    Data() : refcount_(1) {}
+    Data(const std::string & node, const std::string &domain, const std::string & resource) :
+      node_name_(node),
+      domain_name_(domain),
+      resource_name_(resource),
+      refcount_(1) {}
+    const std::string node_name_;
+    const std::string domain_name_;
+    const std::string resource_name_;
+
+    void AddRef() { refcount_++; }
+    void Release() { if (!--refcount_) delete this; }
+  private:
+    int refcount_;
+  };
+
+  Data * data_;
+};
+
+}
+
+
+
+#endif
diff --git a/talk/xmpp/plainsaslhandler.h b/talk/xmpp/plainsaslhandler.h
new file mode 100644
index 0000000..e7d44b9
--- /dev/null
+++ b/talk/xmpp/plainsaslhandler.h
@@ -0,0 +1,80 @@
+/*
+ * 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 _PLAINSASLHANDLER_H_
+#define _PLAINSASLHANDLER_H_
+
+#include "talk/xmpp/saslhandler.h"
+#include <algorithm>
+
+namespace buzz {
+
+class PlainSaslHandler : public SaslHandler {
+public:
+  PlainSaslHandler(const Jid & jid, const talk_base::CryptString & password, 
+      bool allow_plain) : jid_(jid), password_(password), 
+                          allow_plain_(allow_plain) {}
+    
+  virtual ~PlainSaslHandler() {}
+
+  // Should pick the best method according to this handler
+  // returns the empty string if none are suitable
+  virtual std::string ChooseBestSaslMechanism(const std::vector<std::string> & mechanisms, bool encrypted) {
+  
+    if (!encrypted && !allow_plain_) {
+      return "";
+    }
+    
+    std::vector<std::string>::const_iterator it = std::find(mechanisms.begin(), mechanisms.end(), "PLAIN");
+    if (it == mechanisms.end()) {
+      return "";
+    }
+    else {
+      return "PLAIN";
+    }
+  }
+
+  // Creates a SaslMechanism for the given mechanism name (you own it
+  // once you get it).  If not handled, return NULL.
+  virtual SaslMechanism * CreateSaslMechanism(const std::string & mechanism) {
+    if (mechanism == "PLAIN") {
+      return new SaslPlainMechanism(jid_, password_);
+    }
+    return NULL;
+  }
+  
+private:
+  Jid jid_;
+  talk_base::CryptString password_;
+  bool allow_plain_;
+};
+
+
+}
+
+#endif
+
diff --git a/talk/xmpp/prexmppauth.h b/talk/xmpp/prexmppauth.h
new file mode 100644
index 0000000..dce5e0b
--- /dev/null
+++ b/talk/xmpp/prexmppauth.h
@@ -0,0 +1,86 @@
+/*
+ * 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 _PREXMPPAUTH_H_
+#define _PREXMPPAUTH_H_
+
+#include "talk/base/cryptstring.h"
+#include "talk/base/sigslot.h"
+#include "talk/xmpp/saslhandler.h"
+
+namespace talk_base {
+  class SocketAddress;
+}
+
+namespace buzz {
+
+class Jid;
+class SaslMechanism;
+
+class CaptchaChallenge {
+ public:
+  CaptchaChallenge() : captcha_needed_(false) {}
+  CaptchaChallenge(const std::string& token, const std::string& url) 
+    : captcha_needed_(true), captcha_token_(token), captcha_image_url_(url) {
+  }
+
+  bool captcha_needed() const { return captcha_needed_; }
+  const std::string& captcha_token() const { return captcha_token_; }
+
+  // This url is relative to the gaia server.  Once we have better tools
+  // for cracking URLs, we should probably make this a full URL
+  const std::string& captcha_image_url() const { return captcha_image_url_; }
+
+ private:
+  bool captcha_needed_;
+  std::string captcha_token_;
+  std::string captcha_image_url_;
+};
+
+class PreXmppAuth : public SaslHandler {
+public:
+  virtual ~PreXmppAuth() {}
+  
+  virtual void StartPreXmppAuth(
+    const Jid & jid,
+    const talk_base::SocketAddress & server,
+    const talk_base::CryptString & pass,
+    const std::string & auth_cookie) = 0;
+  
+  sigslot::signal0<> SignalAuthDone;
+  
+  virtual bool IsAuthDone() const = 0;
+  virtual bool IsAuthorized() const = 0;
+  virtual bool HadError() const = 0;
+  virtual int GetError() const = 0;
+  virtual CaptchaChallenge GetCaptchaChallenge() const = 0;
+  virtual std::string GetAuthCookie() const = 0;
+};
+
+}
+
+#endif
diff --git a/talk/xmpp/ratelimitmanager.cc b/talk/xmpp/ratelimitmanager.cc
new file mode 100644
index 0000000..14667a7
--- /dev/null
+++ b/talk/xmpp/ratelimitmanager.cc
@@ -0,0 +1,80 @@
+/*
+ * 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 <list>
+#include <string>
+
+#include "talk/xmpp/ratelimitmanager.h"
+
+namespace buzz {
+
+RateLimitManager::RateLimit* RateLimitManager::GetRateLimit(
+    const std::string event_name) {
+  RateLimitMap::iterator it = rate_limits_.find(event_name);
+  if (it != rate_limits_.end()) {
+    return it->second;
+  }
+  return NULL;
+}
+
+bool RateLimitManager::IsWithinRateLimit(const std::string event_name) {
+  RateLimit* current_rate = GetRateLimit(event_name);
+  if (current_rate) {
+    return current_rate->IsWithinRateLimit();
+  }
+  return true; // If no rate limit is set, then you must be under the limit
+}
+
+void RateLimitManager::UpdateRateLimit(const std::string event_name, 
+                                       int max_count, 
+                                       int per_x_seconds) {
+  RateLimit* current_rate = GetRateLimit(event_name);
+  if (!current_rate) {
+    current_rate = new RateLimit(max_count, per_x_seconds);
+    rate_limits_[event_name] = current_rate;
+  }
+  current_rate->UpdateRateLimit();
+}                            
+
+bool RateLimitManager::VerifyRateLimit(const std::string event_name, 
+                                       int max_count, 
+                                       int per_x_seconds) {
+  return VerifyRateLimit(event_name, max_count, per_x_seconds, false);
+}
+
+bool RateLimitManager::VerifyRateLimit(const std::string event_name, 
+                                       int max_count, 
+                                       int per_x_seconds, 
+                                       bool always_update) {
+  bool within_rate_limit = IsWithinRateLimit(event_name);
+  if (within_rate_limit || always_update) {
+    UpdateRateLimit(event_name, max_count, per_x_seconds);
+  }
+  return within_rate_limit;
+}
+
+}
diff --git a/talk/xmpp/ratelimitmanager.h b/talk/xmpp/ratelimitmanager.h
new file mode 100644
index 0000000..1a7fc82
--- /dev/null
+++ b/talk/xmpp/ratelimitmanager.h
@@ -0,0 +1,141 @@
+/*
+ * 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 _RATELIMITMANAGER_H_
+#define _RATELIMITMANAGER_H_
+
+#include "talk/base/time.h"
+#include "talk/base/taskrunner.h"
+#include <map>
+
+namespace buzz {
+
+/////////////////////////////////////////////////////////////////////
+//
+// RATELIMITMANAGER
+//
+/////////////////////////////////////////////////////////////////////
+//
+// RateLimitManager imposes client-side rate limiting for xmpp tasks and
+// other events.  It ensures that no more than i events with a given name 
+// can occur within k seconds. 
+//
+// A buffer tracks the previous max_count events.  Before an event is allowed
+// to occur, it can check its rate limit with a call to VerifyRateLimit.  
+// VerifyRateLimit will look up the i-th to last event and if more than
+// k seconds have passed since then, it will return true and update the 
+// appropriate rate limits.  Else, it will return false. 
+//
+/////////////////////////////////////////////////////////////////////
+
+class RateLimitManager {
+ public:
+
+  RateLimitManager() { };
+  ~RateLimitManager() { 
+    for (RateLimitMap::iterator it = rate_limits_.begin();
+         it != rate_limits_.end(); ++it) {
+      delete it->second;
+    }
+  };
+
+  // Checks if the event is under the defined rate limit and updates the
+  // rate limit if so.  Returns true if it's under the rate limit.
+  bool VerifyRateLimit(const std::string event_name, int max_count, 
+                       int per_x_seconds);
+
+  // Checks if the event is under the defined rate limit and updates the
+  // rate limit if so *or* if always_update = true.  
+  bool VerifyRateLimit(const std::string event_name, int max_count, 
+                       int per_x_seconds, bool always_update);
+
+ private:
+  class RateLimit {
+   public:
+    RateLimit(int max, int per_x_secs) : counter_(0), max_count_(max),
+                                         per_x_seconds_(per_x_secs) {
+      event_times_ = new uint32[max_count_];                                                 
+      for (int i = 0; i < max_count_; i++) {
+        event_times_[i] = 0;
+      }
+    }
+
+    ~RateLimit() {
+      if (event_times_) {
+        delete[] event_times_;
+      }
+    }
+
+    // True iff the current time >= to the next song allowed time
+    bool IsWithinRateLimit() {
+      return (talk_base::TimeSince(NextTimeAllowedForCounter()) >= 0);
+    }
+    
+    // Updates time and counter for rate limit
+    void UpdateRateLimit() {
+      event_times_[counter_] = talk_base::Time();
+      counter_ = (counter_ + 1) % max_count_;
+    }
+
+   private:
+
+    // The time at which the i-th (where i = max_count) event occured
+    uint32 PreviousTimeAtCounter() {
+      return event_times_[counter_];
+    }
+
+    // The time that the next event is allowed to occur
+    uint32 NextTimeAllowedForCounter() {
+      return PreviousTimeAtCounter() + per_x_seconds_ * talk_base::kSecToMsec;
+    }
+
+    int counter_; // count modulo max_count of the current event
+    int max_count_; // max number of events that can occur within per_x_seconds
+    int per_x_seconds_; // interval size for rate limit
+    uint32* event_times_; // buffer of previous max_count event
+  };
+
+  typedef std::map<const std::string, RateLimit*> RateLimitMap;
+
+  // Maps from event name to its rate limit
+  RateLimitMap rate_limits_;
+
+  // Returns rate limit for event with specified name
+  RateLimit* GetRateLimit(const std::string event_name);
+
+  // True iff the current time >= to the next song allowed time
+  bool IsWithinRateLimit(const std::string event_name);
+
+  // Updates time and counter for rate limit
+  void UpdateRateLimit(const std::string event_name, int max_count, 
+                       int per_x_seconds); 
+
+};
+
+}
+
+#endif //_RATELIMITMANAGER_H_
diff --git a/talk/xmpp/saslcookiemechanism.h b/talk/xmpp/saslcookiemechanism.h
new file mode 100644
index 0000000..92cff4d
--- /dev/null
+++ b/talk/xmpp/saslcookiemechanism.h
@@ -0,0 +1,88 @@
+/*
+ * 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 _SASLCOOKIEMECHANISM_H_
+#define _SASLCOOKIEMECHANISM_H_
+
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/saslmechanism.h"
+#include "talk/xmpp/constants.h"
+
+namespace buzz {
+
+class SaslCookieMechanism : public SaslMechanism {
+
+public:
+  SaslCookieMechanism(const std::string & mechanism,
+                      const std::string & username,
+                      const std::string & cookie,
+                      const std::string & token_service)
+    : mechanism_(mechanism),
+      username_(username),
+      cookie_(cookie),
+      token_service_(token_service) {}
+
+  SaslCookieMechanism(const std::string & mechanism,
+                      const std::string & username,
+                      const std::string & cookie)
+    : mechanism_(mechanism),
+      username_(username),
+      cookie_(cookie),
+      token_service_("") {}
+
+  virtual std::string GetMechanismName() { return mechanism_; }
+    
+  virtual XmlElement * StartSaslAuth() {
+    // send initial request
+    XmlElement * el = new XmlElement(QN_SASL_AUTH, true);
+    el->AddAttr(QN_MECHANISM, mechanism_);
+    if (!token_service_.empty()) {
+      el->AddAttr(
+          QName(true, "http://www.google.com/talk/protocol/auth", "service"),
+          token_service_);
+    }
+
+    std::string credential;
+    credential.append("\0", 1);
+    credential.append(username_);
+    credential.append("\0", 1);
+    credential.append(cookie_);
+    el->AddText(Base64Encode(credential));
+    return el;
+  }
+  
+private:
+  std::string mechanism_;
+  std::string username_;
+  std::string cookie_;
+  std::string token_service_;
+};
+
+}
+
+#endif
diff --git a/talk/xmpp/saslhandler.h b/talk/xmpp/saslhandler.h
new file mode 100644
index 0000000..bead8aa
--- /dev/null
+++ b/talk/xmpp/saslhandler.h
@@ -0,0 +1,59 @@
+/*
+ * 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 _SASLHANDLER_H_
+#define _SASLHANDLER_H_
+
+#include <string>
+#include <vector>
+
+namespace buzz {
+
+class XmlElement;
+class SaslMechanism;
+
+// Creates mechanisms to deal with a given mechanism
+class SaslHandler {
+
+public:
+  
+  // Intended to be subclassed
+  virtual ~SaslHandler() {}
+
+  // Should pick the best method according to this handler
+  // returns the empty string if none are suitable
+  virtual std::string ChooseBestSaslMechanism(const std::vector<std::string> & mechanisms, bool encrypted) = 0;
+
+  // Creates a SaslMechanism for the given mechanism name (you own it
+  // once you get it).
+  // If not handled, return NULL.
+  virtual SaslMechanism * CreateSaslMechanism(const std::string & mechanism) = 0;
+};
+
+}
+
+#endif
diff --git a/talk/xmpp/saslmechanism.cc b/talk/xmpp/saslmechanism.cc
new file mode 100644
index 0000000..2645ac0
--- /dev/null
+++ b/talk/xmpp/saslmechanism.cc
@@ -0,0 +1,72 @@
+/*
+ * 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/base/base64.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/saslmechanism.h"
+
+using talk_base::Base64;
+
+namespace buzz {
+
+XmlElement *
+SaslMechanism::StartSaslAuth() {
+  return new XmlElement(QN_SASL_AUTH, true);
+}
+
+XmlElement *
+SaslMechanism::HandleSaslChallenge(const XmlElement * challenge) {
+  return new XmlElement(QN_SASL_ABORT, true);
+}
+
+void
+SaslMechanism::HandleSaslSuccess(const XmlElement * success) {
+}
+
+void
+SaslMechanism::HandleSaslFailure(const XmlElement * failure) {
+}
+
+std::string
+SaslMechanism::Base64Encode(const std::string & plain) {
+  return Base64::Encode(plain);
+}
+
+std::string
+SaslMechanism::Base64Decode(const std::string & encoded) {
+  return Base64::Decode(encoded, Base64::DO_LAX);
+}
+
+std::string
+SaslMechanism::Base64EncodeFromArray(const char * plain, size_t length) {
+  std::string result;
+  Base64::EncodeFromArray(plain, length, &result);
+  return result;
+}
+
+}
diff --git a/talk/xmpp/saslmechanism.h b/talk/xmpp/saslmechanism.h
new file mode 100644
index 0000000..f2e5adc
--- /dev/null
+++ b/talk/xmpp/saslmechanism.h
@@ -0,0 +1,74 @@
+/*
+ * 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 _SASLMECHANISM_H_
+#define _SASLMECHANISM_H_
+
+#include <string>
+
+namespace buzz {
+
+class XmlElement;
+
+
+// Defines a mechnanism to do SASL authentication.
+// Subclass instances should have a self-contained way to present
+// credentials.
+class SaslMechanism {
+
+public:
+  
+  // Intended to be subclassed
+  virtual ~SaslMechanism() {}
+
+  // Should return the name of the SASL mechanism, e.g., "PLAIN"
+  virtual std::string GetMechanismName() = 0;
+
+  // Should generate the initial "auth" request.  Default is just <auth/>.
+  virtual XmlElement * StartSaslAuth();
+
+  // Should respond to a SASL "<challenge>" request.  Default is
+  // to abort (for mechanisms that do not do challenge-response)
+  virtual XmlElement * HandleSaslChallenge(const XmlElement * challenge);
+
+  // Notification of a SASL "<success>".  Sometimes information
+  // is passed on success.
+  virtual void HandleSaslSuccess(const XmlElement * success);
+
+  // Notification of a SASL "<failure>".  Sometimes information
+  // for the user is passed on failure.
+  virtual void HandleSaslFailure(const XmlElement * failure);
+
+protected:
+  static std::string Base64Encode(const std::string & plain);
+  static std::string Base64Decode(const std::string & encoded);
+  static std::string Base64EncodeFromArray(const char * plain, size_t length);
+};
+
+}
+
+#endif
diff --git a/talk/xmpp/saslplainmechanism.h b/talk/xmpp/saslplainmechanism.h
new file mode 100644
index 0000000..72532e6
--- /dev/null
+++ b/talk/xmpp/saslplainmechanism.h
@@ -0,0 +1,65 @@
+/*
+ * 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 _SASLPLAINMECHANISM_H_
+#define _SASLPLAINMECHANISM_H_
+
+#include "talk/base/cryptstring.h"
+#include "talk/xmpp/saslmechanism.h"
+
+namespace buzz {
+
+class SaslPlainMechanism : public SaslMechanism {
+
+public:
+  SaslPlainMechanism(const buzz::Jid user_jid, const talk_base::CryptString & password) :
+    user_jid_(user_jid), password_(password) {}
+
+  virtual std::string GetMechanismName() { return "PLAIN"; }
+    
+  virtual XmlElement * StartSaslAuth() {
+    // send initial request
+    XmlElement * el = new XmlElement(QN_SASL_AUTH, true);
+    el->AddAttr(QN_MECHANISM, "PLAIN");
+
+    talk_base::FormatCryptString credential;
+    credential.Append("\0", 1);
+    credential.Append(user_jid_.node());
+    credential.Append("\0", 1);
+    credential.Append(&password_);
+    el->AddText(Base64EncodeFromArray(credential.GetData(), credential.GetLength()));
+    return el;
+  }
+  
+private:
+  Jid user_jid_;
+  talk_base::CryptString password_;
+};
+
+}
+
+#endif
diff --git a/talk/xmpp/xmppclient.cc b/talk/xmpp/xmppclient.cc
new file mode 100644
index 0000000..d772998
--- /dev/null
+++ b/talk/xmpp/xmppclient.cc
@@ -0,0 +1,407 @@
+/*
+ * 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 "xmppclient.h"
+#include "xmpptask.h"
+#include "talk/xmpp/constants.h"
+#include "talk/base/sigslot.h"
+#include "talk/xmpp/saslplainmechanism.h"
+#include "talk/xmpp/prexmppauth.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/xmpp/plainsaslhandler.h"
+
+namespace buzz {
+
+talk_base::TaskParent* XmppClient::GetParent(int code) {
+  if (code == XMPP_CLIENT_TASK_CODE)
+    return this;
+  else
+    return talk_base::Task::GetParent(code);
+}
+
+class XmppClient::Private :
+    public sigslot::has_slots<>,
+    public XmppSessionHandler,
+    public XmppOutputHandler {
+public:
+
+  Private(XmppClient * client) :
+    client_(client),
+    socket_(NULL),
+    engine_(NULL),
+    proxy_port_(0),
+    pre_engine_error_(XmppEngine::ERROR_NONE),
+    pre_engine_subcode_(0),
+    signal_closed_(false),
+    allow_plain_(false) {}
+
+  // the owner
+  XmppClient * const client_;
+
+  // the two main objects
+  talk_base::scoped_ptr<AsyncSocket> socket_;
+  talk_base::scoped_ptr<XmppEngine> engine_;
+  talk_base::scoped_ptr<PreXmppAuth> pre_auth_;
+  talk_base::CryptString pass_;
+  std::string auth_cookie_;
+  talk_base::SocketAddress server_;
+  std::string proxy_host_;
+  int proxy_port_;
+  XmppEngine::Error pre_engine_error_;
+  int pre_engine_subcode_;
+  CaptchaChallenge captcha_challenge_;
+  bool signal_closed_;
+  bool allow_plain_;
+
+  // implementations of interfaces
+  void OnStateChange(int state);
+  void WriteOutput(const char * bytes, size_t len);
+  void StartTls(const std::string & domainname);
+  void CloseConnection();
+
+  // slots for socket signals
+  void OnSocketConnected();
+  void OnSocketRead();
+  void OnSocketClosed();
+};
+
+XmppReturnStatus
+XmppClient::Connect(const XmppClientSettings & settings, const std::string & lang, AsyncSocket * socket, PreXmppAuth * pre_auth) {
+  if (socket == NULL)
+    return XMPP_RETURN_BADARGUMENT;
+  if (d_->socket_.get() != NULL)
+    return XMPP_RETURN_BADSTATE;
+
+  d_->socket_.reset(socket);
+
+  d_->socket_->SignalConnected.connect(d_.get(), &Private::OnSocketConnected);
+  d_->socket_->SignalRead.connect(d_.get(), &Private::OnSocketRead);
+  d_->socket_->SignalClosed.connect(d_.get(), &Private::OnSocketClosed);
+
+  d_->engine_.reset(XmppEngine::Create());
+  d_->engine_->SetSessionHandler(d_.get());
+  d_->engine_->SetOutputHandler(d_.get());
+  if (!settings.resource().empty()) {
+    d_->engine_->SetRequestedResource(settings.resource());
+  }
+  d_->engine_->SetUseTls(settings.use_tls());
+
+  //
+  // The talk.google.com server expects you to use "gmail.com" in the
+  // stream, and expects the domain certificate to be "gmail.com" as well.
+  // For all other servers, we leave the strings empty, which causes
+  // the jid's domain to be used.  "foo@example.com" -> stream to="example.com"
+  // tls certificate for "example.com"
+  //
+  // This is only true when using Gaia auth, so let's say if there's no preauth,
+  // we should use the actual server name
+  std::string server_name = settings.server().IPAsString();
+  if ((server_name == buzz::STR_TALK_GOOGLE_COM ||
+      server_name == buzz::STR_TALKX_L_GOOGLE_COM) &&
+      pre_auth != NULL) {
+    d_->engine_->SetTlsServer(buzz::STR_GMAIL_COM, buzz::STR_GMAIL_COM);
+  }
+
+  // Set language
+  d_->engine_->SetLanguage(lang);
+
+  d_->engine_->SetUser(buzz::Jid(settings.user(), settings.host(), STR_EMPTY));
+
+  d_->pass_ = settings.pass();
+  d_->auth_cookie_ = settings.auth_cookie();
+  d_->server_ = settings.server();
+  d_->proxy_host_ = settings.proxy_host();
+  d_->proxy_port_ = settings.proxy_port();
+  d_->allow_plain_ = settings.allow_plain();
+  d_->pre_auth_.reset(pre_auth);
+
+  return XMPP_RETURN_OK;
+}
+
+XmppEngine::State
+XmppClient::GetState() {
+  if (d_->engine_.get() == NULL)
+    return XmppEngine::STATE_NONE;
+  return d_->engine_->GetState();
+}
+
+XmppEngine::Error
+XmppClient::GetError(int *subcode) {
+  if (subcode) {
+    *subcode = 0;
+  }
+  if (d_->engine_.get() == NULL)
+    return XmppEngine::ERROR_NONE;
+  if (d_->pre_engine_error_ != XmppEngine::ERROR_NONE) {
+    if (subcode) {
+      *subcode = d_->pre_engine_subcode_;
+    }
+    return d_->pre_engine_error_;
+  }
+  return d_->engine_->GetError(subcode);
+}
+
+const XmlElement *
+XmppClient::GetStreamError() {
+  if (d_->engine_.get() == NULL) {
+    return NULL;
+  }
+  return d_->engine_->GetStreamError();
+}
+
+CaptchaChallenge XmppClient::GetCaptchaChallenge() {
+  if (d_->engine_.get() == NULL)
+    return CaptchaChallenge();
+  return d_->captcha_challenge_;
+}
+
+std::string
+XmppClient::GetAuthCookie() {
+  if (d_->engine_.get() == NULL)
+    return "";
+  return d_->auth_cookie_;
+}
+
+int
+XmppClient::ProcessStart() {
+  if (d_->pre_auth_.get()) {
+    d_->pre_auth_->SignalAuthDone.connect(this, &XmppClient::OnAuthDone);
+    d_->pre_auth_->StartPreXmppAuth(
+        d_->engine_->GetUser(), d_->server_, d_->pass_, d_->auth_cookie_);
+    d_->pass_.Clear(); // done with this;
+    return STATE_PRE_XMPP_LOGIN;
+  }
+  else {
+    d_->engine_->SetSaslHandler(new PlainSaslHandler(
+              d_->engine_->GetUser(), d_->pass_, d_->allow_plain_));
+    d_->pass_.Clear(); // done with this;
+    return STATE_START_XMPP_LOGIN;
+  }
+}
+
+void
+XmppClient::OnAuthDone() {
+  Wake();
+}
+
+int
+XmppClient::ProcessCookieLogin() {
+  // Don't know how this could happen, but crash reports show it as NULL
+  if (!d_->pre_auth_.get()) {
+    d_->pre_engine_error_ = XmppEngine::ERROR_AUTH;
+    EnsureClosed();
+    return STATE_ERROR;
+  }
+
+  // Wait until pre authentication is done is done
+  if (!d_->pre_auth_->IsAuthDone())
+    return STATE_BLOCKED;
+
+  if (!d_->pre_auth_->IsAuthorized()) {
+    // maybe split out a case when gaia is down?
+    if (d_->pre_auth_->HadError()) {
+      d_->pre_engine_error_ = XmppEngine::ERROR_AUTH;
+      d_->pre_engine_subcode_ = d_->pre_auth_->GetError();
+    }
+    else {
+      d_->pre_engine_error_ = XmppEngine::ERROR_UNAUTHORIZED;
+      d_->pre_engine_subcode_ = 0;
+      d_->captcha_challenge_ = d_->pre_auth_->GetCaptchaChallenge();
+    }
+    d_->pre_auth_.reset(NULL); // done with this
+    EnsureClosed();
+    return STATE_ERROR;
+  }
+
+  // Save auth cookie as a result
+  d_->auth_cookie_ = d_->pre_auth_->GetAuthCookie();
+
+  // transfer ownership of pre_auth_ to engine
+  d_->engine_->SetSaslHandler(d_->pre_auth_.release());
+  return STATE_START_XMPP_LOGIN;
+}
+
+int
+XmppClient::ProcessStartXmppLogin() {
+  // Done with pre-connect tasks - connect!
+  if (!d_->socket_->Connect(d_->server_)) {
+    EnsureClosed();
+    return STATE_ERROR;
+  }
+
+  return STATE_RESPONSE;
+}
+
+int
+XmppClient::ProcessResponse() {
+  // Hang around while we are connected.
+  if (!delivering_signal_ && (d_->engine_.get() == NULL ||
+    d_->engine_->GetState() == XmppEngine::STATE_CLOSED))
+    return STATE_DONE;
+  return STATE_BLOCKED;
+}
+
+XmppReturnStatus
+XmppClient::Disconnect() {
+  if (d_->socket_.get() == NULL)
+    return XMPP_RETURN_BADSTATE;
+  d_->engine_->Disconnect();
+  d_->socket_.reset(NULL);
+  return XMPP_RETURN_OK;
+}
+
+XmppClient::XmppClient(TaskParent * parent)
+    : Task(parent),
+      delivering_signal_(false),
+      valid_(false) {
+  d_.reset(new Private(this));
+  valid_ = true;
+}
+
+XmppClient::~XmppClient() {
+  valid_ = false;
+}
+
+const Jid &
+XmppClient::jid() {
+  return d_->engine_->FullJid();
+}
+
+
+std::string
+XmppClient::NextId() {
+  return d_->engine_->NextId();
+}
+
+XmppReturnStatus
+XmppClient::SendStanza(const XmlElement * stanza) {
+  return d_->engine_->SendStanza(stanza);
+}
+
+XmppReturnStatus
+XmppClient::SendStanzaError(const XmlElement * old_stanza, XmppStanzaError xse, const std::string & message) {
+  return d_->engine_->SendStanzaError(old_stanza, xse, message);
+}
+
+XmppReturnStatus
+XmppClient::SendRaw(const std::string & text) {
+  return d_->engine_->SendRaw(text);
+}
+
+XmppEngine*
+XmppClient::engine() {
+  return d_->engine_.get();
+}
+
+void
+XmppClient::Private::OnSocketConnected() {
+  engine_->Connect();
+}
+
+void
+XmppClient::Private::OnSocketRead() {
+  char bytes[4096];
+  size_t bytes_read;
+  for (;;) {
+    if (!socket_->Read(bytes, sizeof(bytes), &bytes_read)) {
+      // TODO: deal with error information
+      return;
+    }
+
+    if (bytes_read == 0)
+      return;
+
+//#ifdef _DEBUG
+    client_->SignalLogInput(bytes, bytes_read);
+//#endif
+
+    engine_->HandleInput(bytes, bytes_read);
+  }
+}
+
+void
+XmppClient::Private::OnSocketClosed() {
+  int code = socket_->GetError();
+  engine_->ConnectionClosed(code);
+}
+
+void
+XmppClient::Private::OnStateChange(int state) {
+  if (state == XmppEngine::STATE_CLOSED) {
+    client_->EnsureClosed();
+  }
+  else {
+    client_->SignalStateChange((XmppEngine::State)state);
+  }
+  client_->Wake();
+}
+
+void
+XmppClient::Private::WriteOutput(const char * bytes, size_t len) {
+
+//#ifdef _DEBUG
+  client_->SignalLogOutput(bytes, len);
+//#endif
+
+  socket_->Write(bytes, len);
+  // TODO: deal with error information
+}
+
+void
+XmppClient::Private::StartTls(const std::string & domain) {
+#if defined(FEATURE_ENABLE_SSL)
+  socket_->StartTls(domain);
+#endif
+}
+
+void
+XmppClient::Private::CloseConnection() {
+  socket_->Close();
+}
+
+void
+XmppClient::AddXmppTask(XmppTask * task, XmppEngine::HandlerLevel level) {
+  d_->engine_->AddStanzaHandler(task, level);
+}
+
+void
+XmppClient::RemoveXmppTask(XmppTask * task) {
+  d_->engine_->RemoveStanzaHandler(task);
+}
+
+void
+XmppClient::EnsureClosed() {
+  if (!d_->signal_closed_) {
+    d_->signal_closed_ = true;
+    delivering_signal_ = true;
+    SignalStateChange(XmppEngine::STATE_CLOSED);
+    delivering_signal_ = false;
+  }
+}
+
+
+}
diff --git a/talk/xmpp/xmppclient.h b/talk/xmpp/xmppclient.h
new file mode 100644
index 0000000..9983794
--- /dev/null
+++ b/talk/xmpp/xmppclient.h
@@ -0,0 +1,163 @@
+/*
+ * 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 _XMPPCLIENT_H_
+#define _XMPPCLIENT_H_
+
+#include <string>
+#include "talk/base/basicdefs.h"
+#include "talk/base/sigslot.h"
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/asyncsocket.h"
+#include "talk/xmpp/xmppclientsettings.h"
+#include "talk/base/task.h"
+
+namespace buzz {
+
+class XmppTask;
+class PreXmppAuth;
+class CaptchaChallenge;
+
+// Just some non-colliding number.  Could have picked "1".
+#define XMPP_CLIENT_TASK_CODE 0x366c1e47
+
+/////////////////////////////////////////////////////////////////////
+//
+// XMPPCLIENT
+//
+/////////////////////////////////////////////////////////////////////
+//
+// See Task first.  XmppClient is a parent task for XmppTasks.
+//
+// XmppClient is a task which is designed to be the parent task for
+// all tasks that depend on a single Xmpp connection.  If you want to,
+// for example, listen for subscription requests forever, then your
+// listener should be a task that is a child of the XmppClient that owns
+// the connection you are using.  XmppClient has all the utility methods
+// that basically drill through to XmppEngine.
+//
+// XmppClient is just a wrapper for XmppEngine, and if I were writing it
+// all over again, I would make XmppClient == XmppEngine.  Why?
+// XmppEngine needs tasks too, for example it has an XmppLoginTask which
+// should just be the same kind of Task instead of an XmppEngine specific
+// thing.  It would help do certain things like GAIA auth cleaner.
+//
+/////////////////////////////////////////////////////////////////////
+
+class XmppClient : public talk_base::Task, public sigslot::has_slots<>
+{
+public:
+  explicit XmppClient(talk_base::TaskParent * parent);
+  ~XmppClient();
+
+  XmppReturnStatus Connect(const XmppClientSettings & settings,
+                           const std::string & lang,
+                           AsyncSocket * socket,
+                           PreXmppAuth * preauth);
+
+  virtual talk_base::TaskParent* GetParent(int code);
+  virtual int ProcessStart();
+  virtual int ProcessResponse();
+  XmppReturnStatus Disconnect();
+  const Jid & jid();
+
+  sigslot::signal1<XmppEngine::State> SignalStateChange;
+  XmppEngine::State GetState();
+  XmppEngine::Error GetError(int *subcode);
+
+  // When there is a <stream:error> stanza, return the stanza
+  // so that they can be handled.
+  const XmlElement *GetStreamError();
+
+  // When there is an authentication error, we may have captcha info
+  // that the user can use to unlock their account
+  CaptchaChallenge GetCaptchaChallenge();
+
+  // When authentication is successful, this returns the service cookie
+  // (if we used GAIA authentication)
+  std::string GetAuthCookie();
+
+  std::string NextId();
+  XmppReturnStatus SendStanza(const XmlElement *stanza);
+  XmppReturnStatus SendRaw(const std::string & text);
+  XmppReturnStatus SendStanzaError(const XmlElement * pelOriginal,
+                       XmppStanzaError code,
+                       const std::string & text);
+
+  XmppEngine* engine();
+
+  sigslot::signal2<const char *, int> SignalLogInput;
+  sigslot::signal2<const char *, int> SignalLogOutput;
+
+private:
+  friend class XmppTask;
+
+  void OnAuthDone();
+
+  // managed tasks and dispatching
+  void AddXmppTask(XmppTask *, XmppEngine::HandlerLevel);
+  void RemoveXmppTask(XmppTask *);
+
+  sigslot::signal0<> SignalDisconnected;
+
+private:
+  // Internal state management
+  enum {
+    STATE_PRE_XMPP_LOGIN = STATE_NEXT,
+    STATE_START_XMPP_LOGIN = STATE_NEXT + 1,
+  };
+  int Process(int state) {
+    switch (state) {
+      case STATE_PRE_XMPP_LOGIN: return ProcessCookieLogin();
+      case STATE_START_XMPP_LOGIN: return ProcessStartXmppLogin();
+      default: return Task::Process(state);
+    }
+  }
+
+  std::string GetStateName(int state) const {
+    switch (state) {
+      case STATE_PRE_XMPP_LOGIN:      return "PRE_XMPP_LOGIN";
+      case STATE_START_XMPP_LOGIN:  return "START_XMPP_LOGIN";
+      default: return Task::GetStateName(state);
+    }
+  }
+
+  int ProcessCookieLogin();
+  int ProcessStartXmppLogin();
+  void EnsureClosed();
+
+  class Private;
+  friend class Private;
+  talk_base::scoped_ptr<Private> d_;
+
+  bool delivering_signal_;
+  bool valid_;
+};
+
+}
+
+#endif
diff --git a/talk/xmpp/xmppclientsettings.h b/talk/xmpp/xmppclientsettings.h
new file mode 100644
index 0000000..4f821ab
--- /dev/null
+++ b/talk/xmpp/xmppclientsettings.h
@@ -0,0 +1,116 @@
+/*
+ * 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 _XMPPCLIENTSETTINGS_H_
+#define _XMPPCLIENTSETTINGS_H_
+
+#include "talk/p2p/base/port.h"
+#include "talk/base/cryptstring.h"
+
+namespace buzz {
+
+class XmppUserSettings {
+ public:
+  XmppUserSettings()
+    : use_tls_(false), 
+      allow_plain_(false) {
+  }
+
+  void set_user(const std::string & user) { user_ = user; }
+  void set_host(const std::string & host) { host_ = host; }
+  void set_pass(const talk_base::CryptString & pass) { pass_ = pass; }
+  void set_auth_cookie(const std::string & cookie) { auth_cookie_ = cookie; }
+  void set_resource(const std::string & resource) { resource_ = resource; }
+  void set_use_tls(bool use_tls) { use_tls_ = use_tls; }
+  void set_allow_plain(bool f) { allow_plain_ = f; }
+  void set_token_service(const std::string & token_service) {
+    token_service_ = token_service;
+  }
+
+  const std::string & user() const { return user_; }
+  const std::string & host() const { return host_; }
+  const talk_base::CryptString & pass() const { return pass_; }
+  const std::string & auth_cookie() const { return auth_cookie_; }
+  const std::string & resource() const { return resource_; }
+  bool use_tls() const { return use_tls_; }
+  bool allow_plain() const { return allow_plain_; }
+  const std::string & token_service() const { return token_service_; }
+
+ private:
+  std::string user_;
+  std::string host_;
+  talk_base::CryptString pass_;
+  std::string auth_cookie_;
+  std::string resource_;
+  bool use_tls_;
+  bool allow_plain_;
+  std::string token_service_;
+};
+
+class XmppClientSettings : public XmppUserSettings {
+ public:
+  XmppClientSettings()
+    : protocol_(cricket::PROTO_TCP),
+      proxy_(talk_base::PROXY_NONE),
+      proxy_port_(80),
+      use_proxy_auth_(false) {
+  }
+
+  void set_server(const talk_base::SocketAddress & server) { 
+      server_ = server; 
+  }
+  void set_protocol(cricket::ProtocolType protocol) { protocol_ = protocol; }
+  void set_proxy(talk_base::ProxyType f) { proxy_ = f; }
+  void set_proxy_host(const std::string & host) { proxy_host_ = host; }
+  void set_proxy_port(int port) { proxy_port_ = port; };
+  void set_use_proxy_auth(bool f) { use_proxy_auth_ = f; }
+  void set_proxy_user(const std::string & user) { proxy_user_ = user; }
+  void set_proxy_pass(const talk_base::CryptString & pass) { proxy_pass_ = pass; }
+
+  const talk_base::SocketAddress & server() const { return server_; }
+  cricket::ProtocolType protocol() const { return protocol_; }
+  talk_base::ProxyType proxy() const { return proxy_; }
+  const std::string & proxy_host() const { return proxy_host_; }
+  int proxy_port() const { return proxy_port_; }
+  bool use_proxy_auth() const { return use_proxy_auth_; }
+  const std::string & proxy_user() const { return proxy_user_; }
+  const talk_base::CryptString & proxy_pass() const { return proxy_pass_; }
+
+ private:
+  talk_base::SocketAddress server_;
+  cricket::ProtocolType protocol_;
+  talk_base::ProxyType proxy_;
+  std::string proxy_host_;
+  int proxy_port_;
+  bool use_proxy_auth_;
+  std::string proxy_user_;
+  talk_base::CryptString proxy_pass_;
+};
+
+}
+
+#endif
diff --git a/talk/xmpp/xmppengine.h b/talk/xmpp/xmppengine.h
new file mode 100644
index 0000000..44ed83d
--- /dev/null
+++ b/talk/xmpp/xmppengine.h
@@ -0,0 +1,341 @@
+/*
+ * 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 _xmppengine_h_
+#define _xmppengine_h_
+
+// also part of the API
+#include "talk/xmpp/jid.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlelement.h"
+
+
+namespace buzz {
+
+class XmppEngine;
+class SaslHandler;
+typedef void * XmppIqCookie;
+
+//! XMPP stanza error codes.
+//! Used in XmppEngine.SendStanzaError().
+enum XmppStanzaError {
+  XSE_BAD_REQUEST,
+  XSE_CONFLICT,
+  XSE_FEATURE_NOT_IMPLEMENTED,
+  XSE_FORBIDDEN,
+  XSE_GONE,
+  XSE_INTERNAL_SERVER_ERROR,
+  XSE_ITEM_NOT_FOUND,
+  XSE_JID_MALFORMED,
+  XSE_NOT_ACCEPTABLE,
+  XSE_NOT_ALLOWED,
+  XSE_PAYMENT_REQUIRED,
+  XSE_RECIPIENT_UNAVAILABLE,
+  XSE_REDIRECT,
+  XSE_REGISTRATION_REQUIRED,
+  XSE_SERVER_NOT_FOUND,
+  XSE_SERVER_TIMEOUT,
+  XSE_RESOURCE_CONSTRAINT,
+  XSE_SERVICE_UNAVAILABLE,
+  XSE_SUBSCRIPTION_REQUIRED,
+  XSE_UNDEFINED_CONDITION,
+  XSE_UNEXPECTED_REQUEST,
+};
+
+// XmppReturnStatus
+//    This is used by API functions to synchronously return status.
+enum XmppReturnStatus {
+  XMPP_RETURN_OK,
+  XMPP_RETURN_BADARGUMENT,
+  XMPP_RETURN_BADSTATE,
+  XMPP_RETURN_PENDING,
+  XMPP_RETURN_UNEXPECTED,
+  XMPP_RETURN_NOTYETIMPLEMENTED,
+};
+
+//! Callback for socket output for an XmppEngine connection.
+//! Register via XmppEngine.SetOutputHandler.  An XmppEngine
+//! can call back to this handler while it is processing
+//! Connect, SendStanza, SendIq, Disconnect, or HandleInput.
+class XmppOutputHandler {
+public:
+  virtual ~XmppOutputHandler() {}
+
+  //! Deliver the specified bytes to the XMPP socket.
+  virtual void WriteOutput(const char * bytes, size_t len) = 0;
+
+  //! Initiate TLS encryption on the socket.
+  //! The implementation must verify that the SSL
+  //! certificate matches the given domainname.
+  virtual void StartTls(const std::string & domainname) = 0;
+
+  //! Called when engine wants the connecton closed.
+  virtual void CloseConnection() = 0;
+};
+
+//! Callback to deliver engine state change notifications
+//! to the object managing the engine.
+class XmppSessionHandler {
+public:
+  virtual ~XmppSessionHandler() {}
+  //! Called when engine changes state. Argument is new state.
+  virtual void OnStateChange(int state) = 0;
+};
+
+//! Callback to deliver stanzas to an Xmpp application module.
+//! Register via XmppEngine.SetDefaultSessionHandler or via
+//! XmppEngine.AddSessionHAndler.  
+class XmppStanzaHandler {
+public:
+  virtual ~XmppStanzaHandler() {}
+  //! Process the given stanza.
+  //! The handler must return true if it has handled the stanza.
+  //! A false return value causes the stanza to be passed on to
+  //! the next registered handler.
+  virtual bool HandleStanza(const XmlElement * stanza) = 0;
+};
+
+//! Callback to deliver iq responses (results and errors).
+//! Register while sending an iq via XmppEngine.SendIq.
+//! Iq responses are routed to matching XmppIqHandlers in preference
+//! to sending to any registered SessionHandlers.
+class XmppIqHandler {
+public:
+  virtual ~XmppIqHandler() {}
+  //! Called to handle the iq response.
+  //! The response may be either a result or an error, and will have
+  //! an 'id' that matches the request and a 'from' that matches the
+  //! 'to' of the request.  Called no more than once; once this is
+  //! called, the handler is automatically unregistered.
+  virtual void IqResponse(XmppIqCookie cookie, const XmlElement * pelStanza) = 0;
+};
+
+//! The XMPP connection engine.
+//! This engine implements the client side of the 'core' XMPP protocol.
+//! To use it, register an XmppOutputHandler to handle socket output
+//! and pass socket input to HandleInput.  Then application code can
+//! set up the connection with a user, password, and other settings,
+//! and then call Connect() to initiate the connection.
+//! An application can listen for events and receive stanzas by
+//! registering an XmppStanzaHandler via AddStanzaHandler().
+class XmppEngine {
+public:
+  static XmppEngine * Create();
+  virtual ~XmppEngine() {}
+
+  //! Error codes. See GetError().
+  enum Error {
+    ERROR_NONE = 0,         //!< No error
+    ERROR_XML,              //!< Malformed XML or encoding error
+    ERROR_STREAM,           //!< XMPP stream error - see GetStreamError()
+    ERROR_VERSION,          //!< XMPP version error
+    ERROR_UNAUTHORIZED,     //!< User is not authorized (rejected credentials)
+    ERROR_TLS,              //!< TLS could not be negotiated
+    ERROR_AUTH,             //!< Authentication could not be negotiated
+    ERROR_BIND,             //!< Resource or session binding could not be negotiated
+    ERROR_CONNECTION_CLOSED,//!< Connection closed by output handler.
+    ERROR_DOCUMENT_CLOSED,  //!< Closed by </stream:stream>
+    ERROR_SOCKET,           //!< Socket error
+    ERROR_NETWORK_TIMEOUT,  //!< Some sort of timeout (eg., we never got the roster)
+    ERROR_MISSING_USERNAME  //!< User has a Google Account but no nickname
+  };
+
+  //! States.  See GetState().
+  enum State {
+    STATE_NONE = 0,        //!< Nonexistent state
+    STATE_START,           //!< Initial state.
+    STATE_OPENING,         //!< Exchanging stream headers, authenticating and so on.
+    STATE_OPEN,            //!< Authenticated and bound.
+    STATE_CLOSED,          //!< Session closed, possibly due to error.
+  };
+
+  // SOCKET INPUT AND OUTPUT ------------------------------------------------
+
+  //! Registers the handler for socket output
+  virtual XmppReturnStatus SetOutputHandler(XmppOutputHandler *pxoh) = 0;
+
+  //! Provides socket input to the engine
+  virtual XmppReturnStatus HandleInput(const char * bytes, size_t len) = 0;
+
+  //! Advises the engine that the socket has closed
+  virtual XmppReturnStatus ConnectionClosed(int subcode) = 0;
+
+  // SESSION SETUP ---------------------------------------------------------
+
+  //! Indicates the (bare) JID for the user to use.
+  virtual XmppReturnStatus SetUser(const Jid & jid)= 0;
+
+  //! Get the login (bare) JID.
+  virtual const Jid & GetUser() = 0;
+
+  //! Provides different methods for credentials for login.
+  //! Takes ownership of this object; deletes when login is done
+  virtual XmppReturnStatus SetSaslHandler(SaslHandler * h) = 0;
+
+  //! Sets whether TLS will be used within the connection (default true).
+  virtual XmppReturnStatus SetUseTls(bool useTls) = 0;
+
+  //! Sets an alternate domain from which we allows TLS certificates.
+  //! This is for use in the case where a we want to allow a proxy to
+  //! serve up its own certificate rather than one owned by the underlying
+  //! domain.
+  virtual XmppReturnStatus SetTlsServer(const std::string & proxy_hostname, 
+                                        const std::string & proxy_domain) = 0;
+
+  //! Gets whether TLS will be used within the connection.
+  virtual bool GetUseTls() = 0;
+
+  //! Sets the request resource name, if any (optional).
+  //! Note that the resource name may be overridden by the server; after
+  //! binding, the actual resource name is available as part of FullJid().
+  virtual XmppReturnStatus SetRequestedResource(const std::string& resource) = 0;
+
+  //! Gets the request resource name.
+  virtual const std::string & GetRequestedResource() = 0;
+
+  //! Sets language
+  virtual void SetLanguage(const std::string & lang) = 0;
+
+  // SESSION MANAGEMENT ---------------------------------------------------
+
+  //! Set callback for state changes.
+  virtual XmppReturnStatus SetSessionHandler(XmppSessionHandler* handler) = 0;
+
+  //! Initiates the XMPP connection.
+  //! After supplying connection settings, call this once to initiate,
+  //! (optionally) encrypt, authenticate, and bind the connection.
+  virtual XmppReturnStatus Connect() = 0;
+
+  //! The current engine state.
+  virtual State GetState() = 0;
+
+  //! Returns true if the connection is encrypted (under TLS)
+  virtual bool IsEncrypted() = 0;
+
+  //! The error code.
+  //! Consult this after XmppOutputHandler.OnClose().
+  virtual Error GetError(int *subcode) = 0;
+
+  //! The stream:error stanza, when the error is XmppEngine::ERROR_STREAM.
+  //! Notice the stanza returned is owned by the XmppEngine and
+  //! is deleted when the engine is destroyed.
+  virtual const XmlElement * GetStreamError() = 0;
+
+  //! Closes down the connection.
+  //! Sends CloseConnection to output, and disconnects and registered
+  //! session handlers.  After Disconnect completes, it is guaranteed
+  //! that no further callbacks will be made.
+  virtual XmppReturnStatus Disconnect() = 0;
+
+  // APPLICATION USE -------------------------------------------------------
+
+  enum HandlerLevel {
+    HL_NONE = 0,
+    HL_PEEK,   //!< Sees messages before all other processing; cannot abort
+    HL_SINGLE, //!< Watches for a single message, e.g., by id and sender
+    HL_SENDER, //!< Watches for a type of message from a specific sender
+    HL_TYPE,   //!< Watches a type of message, e.g., all groupchat msgs
+    HL_ALL,    //!< Watches all messages - gets last shot
+    HL_COUNT,  //!< Count of handler levels
+  };
+
+  //! Adds a listener for session events.
+  //! Stanza delivery is chained to session handlers; the first to
+  //! return 'true' is the last to get each stanza.
+  virtual XmppReturnStatus AddStanzaHandler(XmppStanzaHandler* handler, HandlerLevel level = HL_PEEK) = 0;
+
+  //! Removes a listener for session events.
+  virtual XmppReturnStatus RemoveStanzaHandler(XmppStanzaHandler* handler) = 0;
+
+  //! Sends a stanza to the server.
+  virtual XmppReturnStatus SendStanza(const XmlElement * pelStanza) = 0;
+
+  //! Sends raw text to the server
+  virtual XmppReturnStatus SendRaw(const std::string & text) = 0;
+
+  //! Sends an iq to the server, and registers a callback for the result.
+  //! Returns the cookie passed to the result handler.
+  virtual XmppReturnStatus SendIq(const XmlElement* pelStanza,
+                                  XmppIqHandler* iq_handler,
+                                  XmppIqCookie* cookie) = 0;
+
+  //! Unregisters an iq callback handler given its cookie.
+  //! No callback will come to this handler after it's unregistered.
+  virtual XmppReturnStatus RemoveIqHandler(XmppIqCookie cookie,
+                                      XmppIqHandler** iq_handler) = 0;
+
+
+  //! Forms and sends an error in response to the given stanza.
+  //! Swaps to and from, sets type to "error", and adds error information
+  //! based on the passed code.  Text is optional and may be STR_EMPTY.
+  virtual XmppReturnStatus SendStanzaError(const XmlElement * pelOriginal,
+                                           XmppStanzaError code,
+                                           const std::string & text) = 0;
+
+  //! The fullly bound JID.
+  //! This JID is only valid after binding has succeeded.  If the value
+  //! is JID_NULL, the binding has not succeeded.
+  virtual const Jid & FullJid() = 0;
+
+  //! The next unused iq id for this connection.
+  //! Call this when building iq stanzas, to ensure that each iq
+  //! gets its own unique id.
+  virtual std::string NextId() = 0;
+
+};
+
+}
+
+
+// Move these to a better location
+
+#define XMPP_FAILED(x)                      \
+  ( (x) == buzz::XMPP_RETURN_OK ? false : true)   \
+
+
+#define XMPP_SUCCEEDED(x)                   \
+  ( (x) == buzz::XMPP_RETURN_OK ? true : false)   \
+
+#define IFR(x)                        \
+  do {                                \
+    xmpp_status = (x);                \
+    if (XMPP_FAILED(xmpp_status)) {   \
+      return xmpp_status;             \
+    }                                 \
+  } while (false)                     \
+
+
+#define IFC(x)                        \
+  do {                                \
+    xmpp_status = (x);                \
+    if (XMPP_FAILED(xmpp_status)) {   \
+      goto Cleanup;                   \
+    }                                 \
+  } while (false)                     \
+
+
+#endif
diff --git a/talk/xmpp/xmppengineimpl.cc b/talk/xmpp/xmppengineimpl.cc
new file mode 100644
index 0000000..262c531
--- /dev/null
+++ b/talk/xmpp/xmppengineimpl.cc
@@ -0,0 +1,498 @@
+/*
+ * 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 TRACK_ARRAY_ALLOC_PROBLEM
+
+#include <vector>
+#include <sstream>
+#include <algorithm>
+#include "talk/xmllite/xmlelement.h"
+#include "talk/base/common.h"
+#include "talk/xmpp/xmppengineimpl.h"
+#include "talk/xmpp/xmpplogintask.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmllite/xmlprinter.h"
+#include "talk/xmpp/saslhandler.h"
+
+namespace buzz {
+
+static const std::string XMPP_CLIENT_NAMESPACES[] = {
+  "stream", "http://etherx.jabber.org/streams",
+  "", "jabber:client",
+};
+
+static const size_t XMPP_CLIENT_NAMESPACES_LEN = 4;
+
+XmppEngine * XmppEngine::Create() {
+  return new XmppEngineImpl();
+}
+
+
+XmppEngineImpl::XmppEngineImpl() :
+    stanzaParseHandler_(this),
+    stanzaParser_(&stanzaParseHandler_),
+    engine_entered_(0),
+    user_jid_(JID_EMPTY),
+    password_(),
+    requested_resource_(STR_EMPTY),
+    tls_needed_(true),
+    login_task_(new XmppLoginTask(this)),
+    next_id_(0),
+    bound_jid_(JID_EMPTY),
+    state_(STATE_START),
+    encrypted_(false),
+    error_code_(ERROR_NONE),
+    subcode_(0),
+    stream_error_(NULL),
+    raised_reset_(false),
+    output_handler_(NULL),
+    session_handler_(NULL),
+    iq_entries_(new IqEntryVector()),
+    sasl_handler_(NULL),
+    output_(new std::stringstream()) {
+  for (int i = 0; i < HL_COUNT; i+= 1) {
+    stanza_handlers_[i].reset(new StanzaHandlerVector());
+  }
+}
+
+XmppEngineImpl::~XmppEngineImpl() {
+  DeleteIqCookies();
+}
+
+XmppReturnStatus
+XmppEngineImpl::SetOutputHandler(XmppOutputHandler* output_handler) {
+  if (state_ != STATE_START)
+    return XMPP_RETURN_BADSTATE;
+
+  output_handler_ = output_handler;
+
+  return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus
+XmppEngineImpl::SetSessionHandler(XmppSessionHandler* session_handler) {
+  if (state_ != STATE_START)
+    return XMPP_RETURN_BADSTATE;
+
+  session_handler_ = session_handler;
+
+  return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus
+XmppEngineImpl::HandleInput(const char * bytes, size_t len) {
+  if (state_ < STATE_OPENING || state_ > STATE_OPEN)
+    return XMPP_RETURN_BADSTATE;
+
+  EnterExit ee(this);
+
+  // TODO: The return value of the xml parser is not checked.
+  stanzaParser_.Parse(bytes, len, false);
+
+  return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus
+XmppEngineImpl::ConnectionClosed(int subcode) {
+  if (state_ != STATE_CLOSED) {
+    EnterExit ee(this);
+    // If told that connection closed and not already closed,
+    // then connection was unpexectedly dropped.
+    if (subcode) {
+      SignalError(ERROR_SOCKET, subcode);
+    } else {
+      SignalError(ERROR_CONNECTION_CLOSED, 0);  // no subcode
+    }
+  }
+  return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus
+XmppEngineImpl::SetUseTls(bool useTls) {
+  if (state_ != STATE_START)
+    return XMPP_RETURN_BADSTATE;
+
+  tls_needed_ = useTls;
+
+  return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus
+XmppEngineImpl::SetTlsServer(const std::string & tls_server_hostname,
+                             const std::string & tls_server_domain) {
+  if (state_ != STATE_START)
+    return XMPP_RETURN_BADSTATE;
+
+  tls_server_hostname_ = tls_server_hostname;
+  tls_server_domain_= tls_server_domain;
+
+  return XMPP_RETURN_OK;
+}
+
+bool
+XmppEngineImpl::GetUseTls() {
+  return tls_needed_;
+}
+
+XmppReturnStatus
+XmppEngineImpl::SetUser(const Jid & jid) {
+  if (state_ != STATE_START)
+    return XMPP_RETURN_BADSTATE;
+
+  user_jid_ = jid;
+
+  return XMPP_RETURN_OK;
+}
+
+const Jid &
+XmppEngineImpl::GetUser() {
+  return user_jid_;
+}
+
+XmppReturnStatus
+XmppEngineImpl::SetSaslHandler(SaslHandler * sasl_handler) {
+  if (state_ != STATE_START)
+    return XMPP_RETURN_BADSTATE;
+
+  sasl_handler_.reset(sasl_handler);
+  return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus
+XmppEngineImpl::SetRequestedResource(const std::string & resource) {
+  if (state_ != STATE_START)
+    return XMPP_RETURN_BADSTATE;
+
+  requested_resource_ = resource;
+
+  return XMPP_RETURN_OK;
+}
+
+const std::string &
+XmppEngineImpl::GetRequestedResource() {
+  return requested_resource_;
+}
+
+XmppReturnStatus
+XmppEngineImpl::AddStanzaHandler(XmppStanzaHandler * stanza_handler,
+                                 XmppEngine::HandlerLevel level) {
+  if (state_ == STATE_CLOSED)
+    return XMPP_RETURN_BADSTATE;
+
+  stanza_handlers_[level]->push_back(stanza_handler);
+
+  return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus
+XmppEngineImpl::RemoveStanzaHandler(XmppStanzaHandler * stanza_handler) {
+
+  bool found = false;
+
+  for (int level = 0; level < HL_COUNT; level += 1) {
+    StanzaHandlerVector::iterator new_end =
+      std::remove(stanza_handlers_[level]->begin(),
+      stanza_handlers_[level]->end(),
+      stanza_handler);
+
+    if (new_end != stanza_handlers_[level]->end()) {
+      stanza_handlers_[level]->erase(new_end, stanza_handlers_[level]->end());
+      found = true;
+    }
+  }
+
+  if (!found) {
+    return XMPP_RETURN_BADARGUMENT;
+  }
+
+  return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus
+XmppEngineImpl::Connect() {
+  if (state_ != STATE_START)
+    return XMPP_RETURN_BADSTATE;
+
+  EnterExit ee(this);
+
+  // get the login task started
+  state_ = STATE_OPENING;
+  if (login_task_.get()) {
+    login_task_->IncomingStanza(NULL, false);
+    if (login_task_->IsDone())
+      login_task_.reset();
+  }
+
+  return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus
+XmppEngineImpl::SendStanza(const XmlElement * element) {
+  if (state_ == STATE_CLOSED)
+    return XMPP_RETURN_BADSTATE;
+
+  EnterExit ee(this);
+
+  if (login_task_.get()) {
+    // still handshaking - then outbound stanzas are queued
+    login_task_->OutgoingStanza(element);
+  } else {
+    // handshake done - send straight through
+    InternalSendStanza(element);
+  }
+
+  return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus
+XmppEngineImpl::SendRaw(const std::string & text) {
+  if (state_ == STATE_CLOSED || login_task_.get())
+    return XMPP_RETURN_BADSTATE;
+
+  EnterExit ee(this);
+
+  (*output_) << text;
+
+  return XMPP_RETURN_OK;
+}
+
+std::string
+XmppEngineImpl::NextId() {
+  std::stringstream ss;
+  ss << next_id_++;
+  return ss.str();
+}
+
+XmppReturnStatus
+XmppEngineImpl::Disconnect() {
+
+  if (state_ != STATE_CLOSED) {
+    EnterExit ee(this);
+    if (state_ == STATE_OPEN)
+      *output_ << "</stream:stream>";
+    state_ = STATE_CLOSED;
+  }
+
+  return XMPP_RETURN_OK;
+}
+
+void
+XmppEngineImpl::IncomingStart(const XmlElement * pelStart) {
+  if (HasError() || raised_reset_)
+    return;
+
+  if (login_task_.get()) {
+    // start-stream should go to login task
+    login_task_->IncomingStanza(pelStart, true);
+    if (login_task_->IsDone())
+      login_task_.reset();
+  }
+  else {
+    // if not logging in, it's an error to see a start
+    SignalError(ERROR_XML, 0);
+  }
+}
+
+void
+XmppEngineImpl::IncomingStanza(const XmlElement * stanza) {
+  if (HasError() || raised_reset_)
+    return;
+
+  if (stanza->Name() == QN_STREAM_ERROR) {
+    // Explicit XMPP stream error
+    SignalStreamError(stanza);
+  } else if (login_task_.get()) {
+    // Handle login handshake
+    login_task_->IncomingStanza(stanza, false);
+    if (login_task_->IsDone())
+      login_task_.reset();
+  } else if (HandleIqResponse(stanza)) {
+    // iq is handled by above call
+  } else {
+    // give every "peek" handler a shot at all stanzas
+    for (size_t i = 0; i < stanza_handlers_[HL_PEEK]->size(); i += 1) {
+      (*stanza_handlers_[HL_PEEK])[i]->HandleStanza(stanza);
+    }
+
+    // give other handlers a shot in precedence order, stopping after handled
+    for (int level = HL_SINGLE; level <= HL_ALL; level += 1) {
+      for (size_t i = 0; i < stanza_handlers_[level]->size(); i += 1) {
+        if ((*stanza_handlers_[level])[i]->HandleStanza(stanza))
+          goto Handled;
+      }
+    }
+
+    // If nobody wants to handle a stanza then send back an error.
+    // Only do this for IQ stanzas as messages should probably just be dropped
+    // and presence stanzas should certainly be dropped.
+    std::string type = stanza->Attr(QN_TYPE);
+    if (stanza->Name() == QN_IQ && 
+        !(type == "error" || type == "result")) {
+      SendStanzaError(stanza, XSE_FEATURE_NOT_IMPLEMENTED, STR_EMPTY);
+    }
+  }
+  Handled:
+    ; // handled - we're done
+}
+
+void
+XmppEngineImpl::IncomingEnd(bool isError) {
+  if (HasError() || raised_reset_)
+    return;
+
+  SignalError(isError ? ERROR_XML : ERROR_DOCUMENT_CLOSED, 0);
+}
+
+void
+XmppEngineImpl::InternalSendStart(const std::string & to) {
+  std::string hostname = tls_server_hostname_;
+  if (hostname.empty()) {
+    hostname = to;
+  }
+
+  // If not language is specified, the spec says use *
+  std::string lang = lang_;
+  if (lang.length() == 0)
+    lang = "*";
+
+  // send stream-beginning
+  // note, we put a \r\n at tne end fo the first line to cause non-XMPP
+  // line-oriented servers (e.g., Apache) to reveal themselves more quickly.
+  *output_ << "<stream:stream to=\"" << hostname << "\" "
+           << "xml:lang=\"" << lang << "\" "
+           << "version=\"1.0\" "
+           << "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+           << "xmlns=\"jabber:client\">\r\n";
+}
+
+void
+XmppEngineImpl::InternalSendStanza(const XmlElement * element) {
+  // It should really never be necessary to set a FROM attribute on a stanza.
+  // It is implied by the bind on the stream and if you get it wrong
+  // (by flipping from/to on a message?) the server will close the stream.
+  ASSERT(!element->HasAttr(QN_FROM));
+
+  // TODO: consider caching the XmlPrinter
+  XmlPrinter::PrintXml(output_.get(), element,
+            XMPP_CLIENT_NAMESPACES, XMPP_CLIENT_NAMESPACES_LEN);
+}
+
+std::string
+XmppEngineImpl::ChooseBestSaslMechanism(const std::vector<std::string> & mechanisms, bool encrypted) {
+  return sasl_handler_->ChooseBestSaslMechanism(mechanisms, encrypted);
+}
+
+SaslMechanism *
+XmppEngineImpl::GetSaslMechanism(const std::string & name) {
+  return sasl_handler_->CreateSaslMechanism(name);
+}
+
+void
+XmppEngineImpl::SignalBound(const Jid & fullJid) {
+  if (state_ == STATE_OPENING) {
+    bound_jid_ = fullJid;
+    state_ = STATE_OPEN;
+  }
+}
+
+void
+XmppEngineImpl::SignalStreamError(const XmlElement * pelStreamError) {
+  if (state_ != STATE_CLOSED) {
+    stream_error_.reset(new XmlElement(*pelStreamError));
+    SignalError(ERROR_STREAM, 0);
+  }
+}
+
+void
+XmppEngineImpl::SignalError(Error errorCode, int subCode) {
+  if (state_ != STATE_CLOSED) {
+    error_code_ = errorCode;
+    subcode_ = subCode;
+    state_ = STATE_CLOSED;
+  }
+}
+
+bool
+XmppEngineImpl::HasError() {
+  return error_code_ != ERROR_NONE;
+}
+
+void
+XmppEngineImpl::StartTls(const std::string & domain) {
+  if (output_handler_) {
+    output_handler_->StartTls(
+      tls_server_domain_.empty() ? domain : tls_server_domain_);
+    encrypted_ = true;
+  }
+}
+
+XmppEngineImpl::EnterExit::EnterExit(XmppEngineImpl* engine)
+  : engine_(engine),
+  state_(engine->state_),
+  error_(engine->error_code_) {
+  engine->engine_entered_ += 1;
+}
+
+XmppEngineImpl::EnterExit::~EnterExit()  {
+ XmppEngineImpl* engine = engine_;
+
+ engine->engine_entered_ -= 1;
+
+ bool closing = (engine->state_ != state_ &&
+       engine->state_ == STATE_CLOSED);
+ bool flushing = closing || (engine->engine_entered_ == 0);
+
+ if (engine->output_handler_ && flushing) {
+   std::string output = engine->output_->str();
+   if (output.length() > 0)
+     engine->output_handler_->WriteOutput(output.c_str(), output.length());
+   engine->output_->str("");
+
+   if (closing) {
+     engine->output_handler_->CloseConnection();
+     engine->output_handler_ = 0;
+   }
+ }
+
+ if (engine->engine_entered_)
+   return;
+
+ if (engine->raised_reset_) {
+   engine->stanzaParser_.Reset();
+   engine->raised_reset_ = false;
+ }
+
+ if (engine->session_handler_) {
+   if (engine->state_ != state_)
+     engine->session_handler_->OnStateChange(engine->state_);
+     // Note: Handling of OnStateChange(CLOSED) should allow for the
+     // deletion of the engine, so no members should be accessed
+     // after this line.
+ }
+}
+
+}
diff --git a/talk/xmpp/xmppengineimpl.h b/talk/xmpp/xmppengineimpl.h
new file mode 100644
index 0000000..e5d3dc6
--- /dev/null
+++ b/talk/xmpp/xmppengineimpl.h
@@ -0,0 +1,278 @@
+/*
+ * 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 _xmppengineimpl_h_
+#define _xmppengineimpl_h_
+
+#include <sstream>
+#include <vector>
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/xmppstanzaparser.h"
+
+namespace buzz {
+
+class XmppLoginTask;
+class XmppEngine;
+class XmppIqEntry;
+class SaslHandler;
+class SaslMechanism;
+
+
+//! The XMPP connection engine.
+//! This engine implements the client side of the 'core' XMPP protocol.
+//! To use it, register an XmppOutputHandler to handle socket output
+//! and pass socket input to HandleInput.  Then application code can
+//! set up the connection with a user, password, and other settings,
+//! and then call Connect() to initiate the connection.
+//! An application can listen for events and receive stanzas by
+//! registering an XmppStanzaHandler via AddStanzaHandler().
+class XmppEngineImpl : public XmppEngine {
+public:
+  XmppEngineImpl();
+  virtual ~XmppEngineImpl();
+
+  // SOCKET INPUT AND OUTPUT ------------------------------------------------
+
+  //! Registers the handler for socket output
+  virtual XmppReturnStatus SetOutputHandler(XmppOutputHandler *pxoh);
+
+  //! Provides socket input to the engine
+  virtual XmppReturnStatus HandleInput(const char * bytes, size_t len);
+
+  //! Advises the engine that the socket has closed
+  virtual XmppReturnStatus ConnectionClosed(int subcode);
+
+  // SESSION SETUP ---------------------------------------------------------
+
+  //! Indicates the (bare) JID for the user to use.
+  virtual XmppReturnStatus SetUser(const Jid & jid);
+
+  //! Get the login (bare) JID.
+  virtual const Jid & GetUser();
+
+  //! Indicates the autentication to use.  Takes ownership of the object.
+  virtual XmppReturnStatus SetSaslHandler(SaslHandler * sasl_handler);
+
+  //! Sets whether TLS will be used within the connection (default true).
+  virtual XmppReturnStatus SetUseTls(bool useTls);
+
+  //! Sets an alternate domain from which we allows TLS certificates.
+  //! This is for use in the case where a we want to allow a proxy to
+  //! serve up its own certificate rather than one owned by the underlying
+  //! domain.
+  virtual XmppReturnStatus SetTlsServer(const std::string & proxy_hostname,
+                                        const std::string & proxy_domain);
+
+  //! Gets whether TLS will be used within the connection.
+  virtual bool GetUseTls();
+
+  //! Sets the request resource name, if any (optional).
+  //! Note that the resource name may be overridden by the server; after
+  //! binding, the actual resource name is available as part of FullJid().
+  virtual XmppReturnStatus SetRequestedResource(const std::string& resource);
+
+  //! Gets the request resource name.
+  virtual const std::string & GetRequestedResource();
+
+  //! Sets language
+  virtual void SetLanguage(const std::string & lang) {
+    lang_ = lang;
+  }
+
+  // SESSION MANAGEMENT ---------------------------------------------------
+
+  //! Set callback for state changes.
+  virtual XmppReturnStatus SetSessionHandler(XmppSessionHandler* handler);
+
+  //! Initiates the XMPP connection.
+  //! After supplying connection settings, call this once to initiate,
+  //! (optionally) encrypt, authenticate, and bind the connection.
+  virtual XmppReturnStatus Connect();
+
+  //! The current engine state.
+  virtual State GetState() { return state_; }
+
+  //! Returns true if the connection is encrypted (under TLS)
+  virtual bool IsEncrypted() { return encrypted_; }
+
+  //! The error code.
+  //! Consult this after XmppOutputHandler.OnClose().
+  virtual Error GetError(int *subcode) {
+     if (subcode) {
+       *subcode = subcode_;
+     }
+     return error_code_;
+  }
+
+  //! The stream:error stanza, when the error is XmppEngine::ERROR_STREAM.
+  //! Notice the stanza returned is owned by the XmppEngine and
+  //! is deleted when the engine is destroyed.
+  virtual const XmlElement * GetStreamError() { return stream_error_.get(); }
+
+  //! Closes down the connection.
+  //! Sends CloseConnection to output, and disconnects and registered
+  //! session handlers.  After Disconnect completes, it is guaranteed
+  //! that no further callbacks will be made.
+  virtual XmppReturnStatus Disconnect();
+
+  // APPLICATION USE -------------------------------------------------------
+
+  //! Adds a listener for session events.
+  //! Stanza delivery is chained to session handlers; the first to
+  //! return 'true' is the last to get each stanza.
+  virtual XmppReturnStatus AddStanzaHandler(XmppStanzaHandler* handler,
+                                            XmppEngine::HandlerLevel level);
+
+  //! Removes a listener for session events.
+  virtual XmppReturnStatus RemoveStanzaHandler(XmppStanzaHandler* handler);
+
+  //! Sends a stanza to the server.
+  virtual XmppReturnStatus SendStanza(const XmlElement * pelStanza);
+
+  //! Sends raw text to the server
+  virtual XmppReturnStatus SendRaw(const std::string & text);
+
+  //! Sends an iq to the server, and registers a callback for the result.
+  //! Returns the cookie passed to the result handler.
+  virtual XmppReturnStatus SendIq(const XmlElement* pelStanza,
+                                  XmppIqHandler* iq_handler,
+                                  XmppIqCookie* cookie);
+
+  //! Unregisters an iq callback handler given its cookie.
+  //! No callback will come to this handler after it's unregistered.
+  virtual XmppReturnStatus RemoveIqHandler(XmppIqCookie cookie,
+                                      XmppIqHandler** iq_handler);
+
+  //! Forms and sends an error in response to the given stanza.
+  //! Swaps to and from, sets type to "error", and adds error information
+  //! based on the passed code.  Text is optional and may be STR_EMPTY.
+  virtual XmppReturnStatus SendStanzaError(const XmlElement * pelOriginal,
+                                           XmppStanzaError code,
+                                           const std::string & text);
+
+  //! The fullly bound JID.
+  //! This JID is only valid after binding has succeeded.  If the value
+  //! is JID_NULL, the binding has not succeeded.
+  virtual const Jid & FullJid() { return bound_jid_; }
+
+  //! The next unused iq id for this connection.
+  //! Call this when building iq stanzas, to ensure that each iq
+  //! gets its own unique id.
+  virtual std::string NextId();
+
+private:
+  friend class XmppLoginTask;
+  friend class XmppIqEntry;
+
+  void IncomingStanza(const XmlElement *pelStanza);
+  void IncomingStart(const XmlElement *pelStanza);
+  void IncomingEnd(bool isError);
+
+  void InternalSendStart(const std::string & domainName);
+  void InternalSendStanza(const XmlElement * pelStanza);
+  std::string ChooseBestSaslMechanism(const std::vector<std::string> & mechanisms, bool encrypted);
+  SaslMechanism * GetSaslMechanism(const std::string & name);
+  void SignalBound(const Jid & fullJid);
+  void SignalStreamError(const XmlElement * pelStreamError);
+  void SignalError(Error errorCode, int subCode);
+  bool HasError();
+  void DeleteIqCookies();
+  bool HandleIqResponse(const XmlElement * element);
+  void StartTls(const std::string & domain);
+  void RaiseReset() { raised_reset_ = true; }
+
+  class StanzaParseHandler : public XmppStanzaParseHandler {
+  public:
+    StanzaParseHandler(XmppEngineImpl * outer) : outer_(outer) {}
+    virtual ~StanzaParseHandler() {}
+    virtual void StartStream(const XmlElement * pelStream)
+      { outer_->IncomingStart(pelStream); }
+    virtual void Stanza(const XmlElement * pelStanza)
+      { outer_->IncomingStanza(pelStanza); }
+    virtual void EndStream()
+      { outer_->IncomingEnd(false); }
+    virtual void XmlError()
+      { outer_->IncomingEnd(true); }
+  private:
+    XmppEngineImpl * const outer_;
+  };
+
+  class EnterExit {
+   public:
+    EnterExit(XmppEngineImpl* engine);
+    ~EnterExit();
+   private:
+    XmppEngineImpl* engine_;
+    State state_;
+    Error error_;
+
+  };
+
+  friend class StanzaParseHandler;
+  friend class EnterExit;
+
+  StanzaParseHandler stanzaParseHandler_;
+  XmppStanzaParser stanzaParser_;
+
+
+  // state
+  int engine_entered_;
+  Jid user_jid_;
+  std::string password_;
+  std::string requested_resource_;
+  bool tls_needed_;
+  std::string tls_server_hostname_;
+  std::string tls_server_domain_;
+  talk_base::scoped_ptr<XmppLoginTask> login_task_;
+  std::string lang_;
+
+  int next_id_;
+  Jid bound_jid_;
+  State state_;
+  bool encrypted_;
+  Error error_code_;
+  int subcode_;
+  talk_base::scoped_ptr<XmlElement> stream_error_;
+  bool raised_reset_;
+  XmppOutputHandler* output_handler_;
+  XmppSessionHandler* session_handler_;
+
+  typedef std::vector<XmppStanzaHandler*> StanzaHandlerVector;
+  talk_base::scoped_ptr<StanzaHandlerVector> stanza_handlers_[HL_COUNT];
+
+  typedef std::vector<XmppIqEntry*> IqEntryVector;
+  talk_base::scoped_ptr<IqEntryVector> iq_entries_;
+
+  talk_base::scoped_ptr<SaslHandler> sasl_handler_;
+
+  talk_base::scoped_ptr<std::stringstream> output_;
+};
+
+}
+
+
+#endif
diff --git a/talk/xmpp/xmppengineimpl_iq.cc b/talk/xmpp/xmppengineimpl_iq.cc
new file mode 100644
index 0000000..5834b90
--- /dev/null
+++ b/talk/xmpp/xmppengineimpl_iq.cc
@@ -0,0 +1,277 @@
+/*
+ * 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 <vector>
+#include <algorithm>
+#include "talk/base/common.h"
+#include "talk/xmpp/xmppengineimpl.h"
+#include "talk/xmpp/constants.h"
+
+namespace buzz {
+
+class XmppIqEntry {
+  XmppIqEntry(const std::string & id, const std::string & to,
+               XmppEngine * pxce, XmppIqHandler * iq_handler) :
+    id_(id),
+    to_(to),
+    engine_(pxce),
+    iq_handler_(iq_handler) {
+  }
+
+private:
+  friend class XmppEngineImpl;
+
+  const std::string id_;
+  const std::string to_;
+  XmppEngine * const engine_;
+  XmppIqHandler * const iq_handler_;
+};
+
+
+XmppReturnStatus
+XmppEngineImpl::SendIq(const XmlElement * element, XmppIqHandler * iq_handler,
+  XmppIqCookie* cookie) {
+  if (state_ == STATE_CLOSED)
+    return XMPP_RETURN_BADSTATE;
+  if (NULL == iq_handler)
+    return XMPP_RETURN_BADARGUMENT;
+  if (!element || element->Name() != QN_IQ)
+    return XMPP_RETURN_BADARGUMENT;
+
+  const std::string& type = element->Attr(QN_TYPE);
+  if (type != "get" && type != "set")
+    return XMPP_RETURN_BADARGUMENT;
+
+  if (!element->HasAttr(QN_ID))
+    return XMPP_RETURN_BADARGUMENT;
+  const std::string& id = element->Attr(QN_ID);
+
+  XmppIqEntry * iq_entry = new XmppIqEntry(id,
+                                              element->Attr(QN_TO),
+                                              this, iq_handler);
+  iq_entries_->push_back(iq_entry);
+  SendStanza(element);
+
+  if (cookie)
+    *cookie = iq_entry;
+
+  return XMPP_RETURN_OK;
+}
+
+
+XmppReturnStatus
+XmppEngineImpl::RemoveIqHandler(XmppIqCookie cookie,
+    XmppIqHandler ** iq_handler) {
+
+  std::vector<XmppIqEntry*, std::allocator<XmppIqEntry*> >::iterator pos;
+
+  pos = std::find(iq_entries_->begin(),
+                  iq_entries_->end(),
+                  reinterpret_cast<XmppIqEntry*>(cookie));
+
+  if (pos == iq_entries_->end())
+    return XMPP_RETURN_BADARGUMENT;
+
+  XmppIqEntry* entry = *pos;
+  iq_entries_->erase(pos);
+  if (iq_handler)
+    *iq_handler = entry->iq_handler_;
+  delete entry;
+
+  return XMPP_RETURN_OK;
+}
+
+void
+XmppEngineImpl::DeleteIqCookies() {
+  for (size_t i = 0; i < iq_entries_->size(); i += 1) {
+    XmppIqEntry * iq_entry_ = (*iq_entries_)[i];
+    (*iq_entries_)[i] = NULL;
+    delete iq_entry_;
+  }
+  iq_entries_->clear();
+}
+
+static void
+AecImpl(XmlElement * error_element, const QName & name,
+        const char * type, const char * code) {
+  error_element->AddElement(new XmlElement(QN_ERROR));
+  error_element->AddAttr(QN_CODE, code, 1);
+  error_element->AddAttr(QN_TYPE, type, 1);
+  error_element->AddElement(new XmlElement(name, true), 1);
+}
+
+
+static void
+AddErrorCode(XmlElement * error_element, XmppStanzaError code) {
+  switch (code) {
+    case XSE_BAD_REQUEST:
+      AecImpl(error_element, QN_STANZA_BAD_REQUEST, "modify", "400");
+      break;
+    case XSE_CONFLICT:
+      AecImpl(error_element, QN_STANZA_CONFLICT, "cancel", "409");
+      break;
+    case XSE_FEATURE_NOT_IMPLEMENTED:
+      AecImpl(error_element, QN_STANZA_FEATURE_NOT_IMPLEMENTED,
+              "cancel", "501");
+      break;
+    case XSE_FORBIDDEN:
+      AecImpl(error_element, QN_STANZA_FORBIDDEN, "auth", "403");
+      break;
+    case XSE_GONE:
+      AecImpl(error_element, QN_STANZA_GONE, "modify", "302");
+      break;
+    case XSE_INTERNAL_SERVER_ERROR:
+      AecImpl(error_element, QN_STANZA_INTERNAL_SERVER_ERROR, "wait", "500");
+      break;
+    case XSE_ITEM_NOT_FOUND:
+      AecImpl(error_element, QN_STANZA_ITEM_NOT_FOUND, "cancel", "404");
+      break;
+    case XSE_JID_MALFORMED:
+      AecImpl(error_element, QN_STANZA_JID_MALFORMED, "modify", "400");
+      break;
+    case XSE_NOT_ACCEPTABLE:
+      AecImpl(error_element, QN_STANZA_NOT_ACCEPTABLE, "cancel", "406");
+      break;
+    case XSE_NOT_ALLOWED:
+      AecImpl(error_element, QN_STANZA_NOT_ALLOWED, "cancel", "405");
+      break;
+    case XSE_PAYMENT_REQUIRED:
+      AecImpl(error_element, QN_STANZA_PAYMENT_REQUIRED, "auth", "402");
+      break;
+    case XSE_RECIPIENT_UNAVAILABLE:
+      AecImpl(error_element, QN_STANZA_RECIPIENT_UNAVAILABLE, "wait", "404");
+      break;
+    case XSE_REDIRECT:
+      AecImpl(error_element, QN_STANZA_REDIRECT, "modify", "302");
+      break;
+    case XSE_REGISTRATION_REQUIRED:
+      AecImpl(error_element, QN_STANZA_REGISTRATION_REQUIRED, "auth", "407");
+      break;
+    case XSE_SERVER_NOT_FOUND:
+      AecImpl(error_element, QN_STANZA_REMOTE_SERVER_NOT_FOUND,
+              "cancel", "404");
+      break;
+    case XSE_SERVER_TIMEOUT:
+      AecImpl(error_element, QN_STANZA_REMOTE_SERVER_TIMEOUT, "wait", "502");
+      break;
+    case XSE_RESOURCE_CONSTRAINT:
+      AecImpl(error_element, QN_STANZA_RESOURCE_CONSTRAINT, "wait", "500");
+      break;
+    case XSE_SERVICE_UNAVAILABLE:
+      AecImpl(error_element, QN_STANZA_SERVICE_UNAVAILABLE, "cancel", "503");
+      break;
+    case XSE_SUBSCRIPTION_REQUIRED:
+      AecImpl(error_element, QN_STANZA_SUBSCRIPTION_REQUIRED, "auth", "407");
+      break;
+    case XSE_UNDEFINED_CONDITION:
+      AecImpl(error_element, QN_STANZA_UNDEFINED_CONDITION, "wait", "500");
+      break;
+    case XSE_UNEXPECTED_REQUEST:
+      AecImpl(error_element, QN_STANZA_UNEXPECTED_REQUEST, "wait", "400");
+      break;
+  }
+}
+
+
+XmppReturnStatus
+XmppEngineImpl::SendStanzaError(const XmlElement * element_original,
+                                XmppStanzaError code,
+                                const std::string & text) {
+
+  if (state_ == STATE_CLOSED)
+    return XMPP_RETURN_BADSTATE;
+
+  XmlElement error_element(element_original->Name());
+  error_element.AddAttr(QN_TYPE, "error");
+
+  // copy attrs, copy 'from' to 'to' and strip 'from'
+  for (const XmlAttr * attribute = element_original->FirstAttr();
+       attribute; attribute = attribute->NextAttr()) {
+    QName name = attribute->Name();
+    if (name == QN_TO)
+      continue; // no need to put a from attr.  Server will stamp stanza
+    else if (name == QN_FROM)
+      name = QN_TO;
+    else if (name == QN_TYPE)
+      continue;
+    error_element.AddAttr(name, attribute->Value());
+  }
+
+  // copy children
+  for (const XmlChild * child = element_original->FirstChild();
+       child;
+       child = child->NextChild()) {
+    if (child->IsText()) {
+      error_element.AddText(child->AsText()->Text());
+    } else {
+      error_element.AddElement(new XmlElement(*(child->AsElement())));
+    }
+  }
+
+  // add error information
+  AddErrorCode(&error_element, code);
+  if (text != STR_EMPTY) {
+    XmlElement * text_element = new XmlElement(QN_STANZA_TEXT, true);
+    text_element->AddText(text);
+    error_element.AddElement(text_element);
+  }
+
+  SendStanza(&error_element);
+
+  return XMPP_RETURN_OK;
+}
+
+
+bool
+XmppEngineImpl::HandleIqResponse(const XmlElement * element) {
+  if (iq_entries_->empty())
+    return false;
+  if (element->Name() != QN_IQ)
+    return false;
+  std::string type = element->Attr(QN_TYPE);
+  if (type != "result" && type != "error")
+    return false;
+  if (!element->HasAttr(QN_ID))
+    return false;
+  std::string id = element->Attr(QN_ID);
+  std::string from = element->Attr(QN_FROM);
+
+  for (std::vector<XmppIqEntry *>::iterator it = iq_entries_->begin();
+       it != iq_entries_->end(); it += 1) {
+    XmppIqEntry * iq_entry = *it;
+    if (iq_entry->id_ == id && iq_entry->to_ == from) {
+      iq_entries_->erase(it);
+      iq_entry->iq_handler_->IqResponse(iq_entry, element);
+      delete iq_entry;
+      return true;
+    }
+  }
+
+  return false;
+}
+
+}
diff --git a/talk/xmpp/xmpplogintask.cc b/talk/xmpp/xmpplogintask.cc
new file mode 100644
index 0000000..537818c
--- /dev/null
+++ b/talk/xmpp/xmpplogintask.cc
@@ -0,0 +1,380 @@
+/*
+ * 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 <iostream>
+#include <string>
+#include <vector>
+#include "talk/base/base64.h"
+#include "talk/base/common.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/jid.h"
+#include "talk/xmpp/saslmechanism.h"
+#include "talk/xmpp/xmppengineimpl.h"
+#include "talk/xmpp/xmpplogintask.h"
+
+using talk_base::ConstantLabel;
+
+namespace buzz {
+
+#ifdef _DEBUG
+const ConstantLabel XmppLoginTask::LOGINTASK_STATES[] = {
+  KLABEL(LOGINSTATE_INIT),
+  KLABEL(LOGINSTATE_STREAMSTART_SENT),
+  KLABEL(LOGINSTATE_STARTED_XMPP),
+  KLABEL(LOGINSTATE_TLS_INIT),
+  KLABEL(LOGINSTATE_AUTH_INIT),
+  KLABEL(LOGINSTATE_BIND_INIT),
+  KLABEL(LOGINSTATE_TLS_REQUESTED),
+  KLABEL(LOGINSTATE_SASL_RUNNING),
+  KLABEL(LOGINSTATE_BIND_REQUESTED),
+  KLABEL(LOGINSTATE_SESSION_REQUESTED),
+  KLABEL(LOGINSTATE_DONE),
+  LASTLABEL
+};
+#endif  // _DEBUG
+
+XmppLoginTask::XmppLoginTask(XmppEngineImpl * pctx) :
+  pctx_(pctx),
+  authNeeded_(true),
+  state_(LOGINSTATE_INIT),
+  pelStanza_(NULL),
+  isStart_(false),
+  iqId_(STR_EMPTY),
+  pelFeatures_(NULL),
+  fullJid_(STR_EMPTY),
+  streamId_(STR_EMPTY),
+  pvecQueuedStanzas_(new std::vector<XmlElement *>()),
+  sasl_mech_(NULL) {
+}
+
+XmppLoginTask::~XmppLoginTask() {
+  for (size_t i = 0; i < pvecQueuedStanzas_->size(); i += 1)
+    delete (*pvecQueuedStanzas_)[i];
+}
+
+void
+XmppLoginTask::IncomingStanza(const XmlElement *element, bool isStart) {
+  pelStanza_ = element;
+  isStart_ = isStart;
+  Advance();
+  pelStanza_ = NULL;
+  isStart_ = false;
+}
+
+const XmlElement *
+XmppLoginTask::NextStanza() {
+  const XmlElement * result = pelStanza_;
+  pelStanza_ = NULL;
+  return result;
+}
+
+bool
+XmppLoginTask::Advance() {
+
+  for (;;) {
+
+    const XmlElement * element = NULL;
+
+#if _DEBUG
+    LOG(LS_VERBOSE) << "XmppLoginTask::Advance - "
+      << talk_base::ErrorName(state_, LOGINTASK_STATES);
+#endif  // _DEBUG
+
+    switch (state_) {
+
+      case LOGINSTATE_INIT: {
+        pctx_->RaiseReset();
+        pelFeatures_.reset(NULL);
+
+        // The proper domain to verify against is the real underlying
+        // domain - i.e., the domain that owns the JID.  Our XmppEngineImpl
+        // also allows matching against a proxy domain instead, if it is told
+        // to do so - see the implementation of XmppEngineImpl::StartTls and
+        // XmppEngine::SetTlsServerDomain to see how you can use that feature
+        pctx_->InternalSendStart(pctx_->user_jid_.domain());
+        state_ = LOGINSTATE_STREAMSTART_SENT;
+        break;
+      }
+
+      case LOGINSTATE_STREAMSTART_SENT: {
+        if (NULL == (element = NextStanza()))
+          return true;
+
+        if (!isStart_ || !HandleStartStream(element))
+          return Failure(XmppEngine::ERROR_VERSION);
+
+        state_ = LOGINSTATE_STARTED_XMPP;
+        return true;
+      }
+
+      case LOGINSTATE_STARTED_XMPP: {
+        if (NULL == (element = NextStanza()))
+          return true;
+
+        if (!HandleFeatures(element))
+          return Failure(XmppEngine::ERROR_VERSION);
+
+        // Use TLS if forced, or if available
+        if (pctx_->tls_needed_ || GetFeature(QN_TLS_STARTTLS) != NULL) {
+          state_ = LOGINSTATE_TLS_INIT;
+          continue;
+        }
+
+        if (authNeeded_) {
+          state_ = LOGINSTATE_AUTH_INIT;
+          continue;
+        }
+
+        state_ = LOGINSTATE_BIND_INIT;
+        continue;
+      }
+
+      case LOGINSTATE_TLS_INIT: {
+        const XmlElement * pelTls = GetFeature(QN_TLS_STARTTLS);
+        if (!pelTls)
+          return Failure(XmppEngine::ERROR_TLS);
+
+        XmlElement el(QN_TLS_STARTTLS, true);
+        pctx_->InternalSendStanza(&el);
+        state_ = LOGINSTATE_TLS_REQUESTED;
+        continue;
+      }
+
+      case LOGINSTATE_TLS_REQUESTED: {
+        if (NULL == (element = NextStanza()))
+          return true;
+        if (element->Name() != QN_TLS_PROCEED)
+          return Failure(XmppEngine::ERROR_TLS);
+
+        // The proper domain to verify against is the real underlying
+        // domain - i.e., the domain that owns the JID.  Our XmppEngineImpl
+        // also allows matching against a proxy domain instead, if it is told
+        // to do so - see the implementation of XmppEngineImpl::StartTls and
+        // XmppEngine::SetTlsServerDomain to see how you can use that feature
+        pctx_->StartTls(pctx_->user_jid_.domain());
+        pctx_->tls_needed_ = false;
+        state_ = LOGINSTATE_INIT;
+        continue;
+      }
+
+      case LOGINSTATE_AUTH_INIT: {
+        const XmlElement * pelSaslAuth = GetFeature(QN_SASL_MECHANISMS);
+        if (!pelSaslAuth) {
+          return Failure(XmppEngine::ERROR_AUTH);
+        }
+
+        // Collect together the SASL auth mechanisms presented by the server
+        std::vector<std::string> mechanisms;
+        for (const XmlElement * pelMech =
+             pelSaslAuth->FirstNamed(QN_SASL_MECHANISM);
+             pelMech;
+             pelMech = pelMech->NextNamed(QN_SASL_MECHANISM)) {
+
+          mechanisms.push_back(pelMech->BodyText());
+        }
+
+        // Given all the mechanisms, choose the best
+        std::string choice(pctx_->ChooseBestSaslMechanism(mechanisms, pctx_->IsEncrypted()));
+        if (choice.empty()) {
+          return Failure(XmppEngine::ERROR_AUTH);
+        }
+
+        // No recognized auth mechanism - that's an error
+        sasl_mech_.reset(pctx_->GetSaslMechanism(choice));
+        if (sasl_mech_.get() == NULL) {
+          return Failure(XmppEngine::ERROR_AUTH);
+        }
+
+        // OK, let's start it.
+        XmlElement * auth = sasl_mech_->StartSaslAuth();
+        if (auth == NULL) {
+          return Failure(XmppEngine::ERROR_AUTH);
+        }
+
+        pctx_->InternalSendStanza(auth);
+        delete auth;
+        state_ = LOGINSTATE_SASL_RUNNING;
+        continue;
+      }
+
+      case LOGINSTATE_SASL_RUNNING: {
+        if (NULL == (element = NextStanza()))
+          return true;
+        if (element->Name().Namespace() != NS_SASL)
+          return Failure(XmppEngine::ERROR_AUTH);
+        if (element->Name() == QN_SASL_CHALLENGE) {
+          XmlElement * response = sasl_mech_->HandleSaslChallenge(element);
+          if (response == NULL) {
+            return Failure(XmppEngine::ERROR_AUTH);
+          }
+          pctx_->InternalSendStanza(response);
+          delete response;
+          state_ = LOGINSTATE_SASL_RUNNING;
+          continue;
+        }
+        if (element->Name() != QN_SASL_SUCCESS) {
+          return Failure(XmppEngine::ERROR_UNAUTHORIZED);
+        }
+
+        // Authenticated!
+        authNeeded_ = false;
+        state_ = LOGINSTATE_INIT;
+        continue;
+      }
+
+      case LOGINSTATE_BIND_INIT: {
+        const XmlElement * pelBindFeature = GetFeature(QN_BIND_BIND);
+        const XmlElement * pelSessionFeature = GetFeature(QN_SESSION_SESSION);
+        if (!pelBindFeature || !pelSessionFeature)
+          return Failure(XmppEngine::ERROR_BIND);
+
+        XmlElement iq(QN_IQ);
+        iq.AddAttr(QN_TYPE, "set");
+
+        iqId_ = pctx_->NextId();
+        iq.AddAttr(QN_ID, iqId_);
+        iq.AddElement(new XmlElement(QN_BIND_BIND, true));
+
+        if (pctx_->requested_resource_ != STR_EMPTY) {
+          iq.AddElement(new XmlElement(QN_BIND_RESOURCE), 1);
+          iq.AddText(pctx_->requested_resource_, 2);
+        }
+        pctx_->InternalSendStanza(&iq);
+        state_ = LOGINSTATE_BIND_REQUESTED;
+        continue;
+      }
+
+      case LOGINSTATE_BIND_REQUESTED: {
+        if (NULL == (element = NextStanza()))
+          return true;
+
+        if (element->Name() != QN_IQ || element->Attr(QN_ID) != iqId_ ||
+            element->Attr(QN_TYPE) == "get" || element->Attr(QN_TYPE) == "set")
+          return true;
+
+        if (element->Attr(QN_TYPE) != "result" || element->FirstElement() == NULL ||
+            element->FirstElement()->Name() != QN_BIND_BIND)
+          return Failure(XmppEngine::ERROR_BIND);
+
+        fullJid_ = Jid(element->FirstElement()->TextNamed(QN_BIND_JID));
+        if (!fullJid_.IsFull()) {
+          return Failure(XmppEngine::ERROR_BIND);
+        }
+
+        // now request session
+        XmlElement iq(QN_IQ);
+        iq.AddAttr(QN_TYPE, "set");
+
+        iqId_ = pctx_->NextId();
+        iq.AddAttr(QN_ID, iqId_);
+        iq.AddElement(new XmlElement(QN_SESSION_SESSION, true));
+        pctx_->InternalSendStanza(&iq);
+
+        state_ = LOGINSTATE_SESSION_REQUESTED;
+        continue;
+      }
+
+      case LOGINSTATE_SESSION_REQUESTED: {
+        if (NULL == (element = NextStanza()))
+          return true;
+        if (element->Name() != QN_IQ || element->Attr(QN_ID) != iqId_ ||
+            element->Attr(QN_TYPE) == "get" || element->Attr(QN_TYPE) == "set")
+          return false;
+
+        if (element->Attr(QN_TYPE) != "result")
+          return Failure(XmppEngine::ERROR_BIND);
+
+        pctx_->SignalBound(fullJid_);
+        FlushQueuedStanzas();
+        state_ = LOGINSTATE_DONE;
+        return true;
+      }
+
+      case LOGINSTATE_DONE:
+        return false;
+    }
+  }
+}
+
+bool
+XmppLoginTask::HandleStartStream(const XmlElement *element) {
+
+  if (element->Name() != QN_STREAM_STREAM)
+    return false;
+
+  if (element->Attr(QN_XMLNS) != "jabber:client")
+    return false;
+
+  if (element->Attr(QN_VERSION) != "1.0")
+    return false;
+
+  if (!element->HasAttr(QN_ID))
+    return false;
+
+  streamId_ = element->Attr(QN_ID);
+
+  return true;
+}
+
+bool
+XmppLoginTask::HandleFeatures(const XmlElement *element) {
+  if (element->Name() != QN_STREAM_FEATURES)
+    return false;
+
+  pelFeatures_.reset(new XmlElement(*element));
+  return true;
+}
+
+const XmlElement *
+XmppLoginTask::GetFeature(const QName & name) {
+  return pelFeatures_->FirstNamed(name);
+}
+
+bool
+XmppLoginTask::Failure(XmppEngine::Error reason) {
+  state_ = LOGINSTATE_DONE;
+  pctx_->SignalError(reason, 0);
+  return false;
+}
+
+void
+XmppLoginTask::OutgoingStanza(const XmlElement * element) {
+  XmlElement * pelCopy = new XmlElement(*element);
+  pvecQueuedStanzas_->push_back(pelCopy);
+}
+
+void
+XmppLoginTask::FlushQueuedStanzas() {
+  for (size_t i = 0; i < pvecQueuedStanzas_->size(); i += 1) {
+    pctx_->InternalSendStanza((*pvecQueuedStanzas_)[i]);
+    delete (*pvecQueuedStanzas_)[i];
+  }
+  pvecQueuedStanzas_->clear();
+}
+
+}
diff --git a/talk/xmpp/xmpplogintask.h b/talk/xmpp/xmpplogintask.h
new file mode 100644
index 0000000..a6507b5
--- /dev/null
+++ b/talk/xmpp/xmpplogintask.h
@@ -0,0 +1,98 @@
+/*
+ * 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 _logintask_h_
+#define _logintask_h_
+
+#include <string>
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/xmpp/jid.h"
+#include "talk/xmpp/xmppengine.h"
+
+namespace buzz {
+
+class XmlElement;
+class XmppEngineImpl;
+class SaslMechanism;
+
+
+class XmppLoginTask {
+
+public:
+  XmppLoginTask(XmppEngineImpl *pctx);
+  ~XmppLoginTask();
+
+  bool IsDone()
+    { return state_ == LOGINSTATE_DONE; }
+  void IncomingStanza(const XmlElement * element, bool isStart);
+  void OutgoingStanza(const XmlElement *element);
+
+private:
+  enum LoginTaskState {
+    LOGINSTATE_INIT = 0,
+    LOGINSTATE_STREAMSTART_SENT,
+    LOGINSTATE_STARTED_XMPP,
+    LOGINSTATE_TLS_INIT,
+    LOGINSTATE_AUTH_INIT,
+    LOGINSTATE_BIND_INIT,
+    LOGINSTATE_TLS_REQUESTED,
+    LOGINSTATE_SASL_RUNNING,
+    LOGINSTATE_BIND_REQUESTED,
+    LOGINSTATE_SESSION_REQUESTED,
+    LOGINSTATE_DONE,
+  };
+
+  const XmlElement * NextStanza();
+  bool Advance();
+  bool HandleStartStream(const XmlElement * element);
+  bool HandleFeatures(const XmlElement * element);
+  const XmlElement * GetFeature(const QName & name);
+  bool Failure(XmppEngine::Error reason);
+  void FlushQueuedStanzas();
+
+  XmppEngineImpl * pctx_;
+  bool authNeeded_;
+  LoginTaskState state_;
+  const XmlElement * pelStanza_;
+  bool isStart_;
+  std::string iqId_;
+  talk_base::scoped_ptr<XmlElement> pelFeatures_;
+  Jid fullJid_;
+  std::string streamId_;
+  talk_base::scoped_ptr<std::vector<XmlElement *> > pvecQueuedStanzas_;
+
+  talk_base::scoped_ptr<SaslMechanism> sasl_mech_;
+
+#ifdef _DEBUG
+  static const talk_base::ConstantLabel LOGINTASK_STATES[];
+#endif  // _DEBUG
+};
+
+}
+
+#endif
diff --git a/talk/xmpp/xmppstanzaparser.cc b/talk/xmpp/xmppstanzaparser.cc
new file mode 100644
index 0000000..3aced15
--- /dev/null
+++ b/talk/xmpp/xmppstanzaparser.cc
@@ -0,0 +1,105 @@
+/*
+ * 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/xmllite/xmlelement.h"
+#include "talk/base/common.h"
+#include "talk/xmpp/xmppstanzaparser.h"
+#include "talk/xmpp/constants.h"
+#ifdef EXPAT_RELATIVE_PATH
+#include "lib/expat.h"
+#else
+#include "third_party/expat/v2_0_1/Source/lib/expat.h"
+#endif
+
+namespace buzz {
+
+XmppStanzaParser::XmppStanzaParser(XmppStanzaParseHandler *psph) :
+  psph_(psph),
+  innerHandler_(this),
+  parser_(&innerHandler_),
+  depth_(0),
+  builder_() {
+}
+
+void
+XmppStanzaParser::Reset() {
+  parser_.Reset();
+  depth_ = 0;
+  builder_.Reset();
+}
+
+void
+XmppStanzaParser::IncomingStartElement(
+    XmlParseContext * pctx, const char * name, const char ** atts) {
+  if (depth_++ == 0) {
+    XmlElement * pelStream = XmlBuilder::BuildElement(pctx, name, atts);
+    if (pelStream == NULL) {
+      pctx->RaiseError(XML_ERROR_SYNTAX);
+      return;
+    }
+    psph_->StartStream(pelStream);
+    delete pelStream;
+    return;
+  }
+
+  builder_.StartElement(pctx, name, atts);
+}
+
+void
+XmppStanzaParser::IncomingCharacterData(
+    XmlParseContext * pctx, const char * text, int len) {
+  if (depth_ > 1) {
+    builder_.CharacterData(pctx, text, len);
+  }
+}
+
+void
+XmppStanzaParser::IncomingEndElement(
+    XmlParseContext * pctx, const char * name) {
+  if (--depth_ == 0) {
+    psph_->EndStream();
+    return;
+  }
+
+  builder_.EndElement(pctx, name);
+
+  if (depth_ == 1) {
+    XmlElement *element = builder_.CreateElement();
+    psph_->Stanza(element);
+    delete element;
+  }
+}
+
+void
+XmppStanzaParser::IncomingError(
+    XmlParseContext * pctx, XML_Error errCode) {
+  UNUSED(pctx);
+  UNUSED(errCode);
+  psph_->XmlError();
+}
+
+}
diff --git a/talk/xmpp/xmppstanzaparser.h b/talk/xmpp/xmppstanzaparser.h
new file mode 100644
index 0000000..c6f8b08
--- /dev/null
+++ b/talk/xmpp/xmppstanzaparser.h
@@ -0,0 +1,97 @@
+/*
+ * 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 _xmppstanzaparser_h_
+#define _xmppstanzaparser_h_
+
+#include "talk/xmllite/xmlparser.h"
+#include "talk/xmllite/xmlbuilder.h"
+
+
+namespace buzz {
+
+class XmlElement;
+
+class XmppStanzaParseHandler {
+public:
+  virtual ~XmppStanzaParseHandler() {}
+  virtual void StartStream(const XmlElement * pelStream) = 0;
+  virtual void Stanza(const XmlElement * pelStanza) = 0;
+  virtual void EndStream() = 0;
+  virtual void XmlError() = 0;
+};
+
+class XmppStanzaParser {
+public:
+  XmppStanzaParser(XmppStanzaParseHandler *psph);
+  bool Parse(const char * data, size_t len, bool isFinal)
+    { return parser_.Parse(data, len, isFinal); }
+  void Reset();
+
+private:
+  class ParseHandler : public XmlParseHandler {
+  public:
+    ParseHandler(XmppStanzaParser * outer) : outer_(outer) {}
+    virtual void StartElement(XmlParseContext * pctx,
+               const char * name, const char ** atts)
+      { outer_->IncomingStartElement(pctx, name, atts); }
+    virtual void EndElement(XmlParseContext * pctx,
+               const char * name)
+      { outer_->IncomingEndElement(pctx, name); }
+    virtual void CharacterData(XmlParseContext * pctx,
+               const char * text, int len)
+      { outer_->IncomingCharacterData(pctx, text, len); }
+    virtual void Error(XmlParseContext * pctx,
+               XML_Error errCode)
+      { outer_->IncomingError(pctx, errCode); }
+  private:
+    XmppStanzaParser * const outer_;
+  };
+
+  friend class ParseHandler;
+
+  void IncomingStartElement(XmlParseContext * pctx,
+               const char * name, const char ** atts);
+  void IncomingEndElement(XmlParseContext * pctx,
+               const char * name);
+  void IncomingCharacterData(XmlParseContext * pctx,
+               const char * text, int len);
+  void IncomingError(XmlParseContext * pctx,
+               XML_Error errCode);
+
+  XmppStanzaParseHandler * psph_;
+  ParseHandler innerHandler_;
+  XmlParser parser_;
+  int depth_;
+  XmlBuilder builder_;
+
+ };
+
+
+}
+
+#endif
diff --git a/talk/xmpp/xmpptask.cc b/talk/xmpp/xmpptask.cc
new file mode 100644
index 0000000..bbd50e3
--- /dev/null
+++ b/talk/xmpp/xmpptask.cc
@@ -0,0 +1,175 @@
+/*
+ * 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 "talk/xmpp/xmpptask.h"
+#include "talk/xmpp/xmppclient.h"
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/ratelimitmanager.h"
+
+namespace buzz {
+
+RateLimitManager task_rate_manager;
+
+XmppTask::XmppTask(TaskParent* parent, XmppEngine::HandlerLevel level)
+    : Task(parent), client_(NULL) {
+#ifdef _DEBUG
+  debug_force_timeout_ = false;
+#endif
+
+  XmppClient* client =
+      static_cast<XmppClient*>(parent->GetParent(XMPP_CLIENT_TASK_CODE));
+  client_ = client;
+  id_ = client->NextId();
+  client->AddXmppTask(this, level);
+  client->SignalDisconnected.connect(this, &XmppTask::OnDisconnect);
+}
+
+XmppTask::~XmppTask() {
+  StopImpl();
+}
+
+void XmppTask::StopImpl() {
+  while (NextStanza() != NULL) {}
+  if (client_) {
+    client_->RemoveXmppTask(this);
+    client_->SignalDisconnected.disconnect(this);
+    client_ = NULL;
+  }
+}
+
+XmppReturnStatus XmppTask::SendStanza(const XmlElement* stanza) {
+  if (client_ == NULL)
+    return XMPP_RETURN_BADSTATE;
+  return client_->SendStanza(stanza);
+}
+
+XmppReturnStatus XmppTask::SendStanzaError(const XmlElement* element_original,
+                                           XmppStanzaError code,
+                                           const std::string& text) {
+  if (client_ == NULL)
+    return XMPP_RETURN_BADSTATE;
+  return client_->SendStanzaError(element_original, code, text);
+}
+
+void XmppTask::Stop() {
+  StopImpl();
+  Task::Stop();
+}
+
+void XmppTask::OnDisconnect() {
+  Error();
+}
+
+void XmppTask::QueueStanza(const XmlElement* stanza) {
+#ifdef _DEBUG
+  if (debug_force_timeout_)
+    return;
+#endif
+
+  stanza_queue_.push_back(new XmlElement(*stanza));
+  Wake();
+}
+
+const XmlElement* XmppTask::NextStanza() {
+  XmlElement* result = NULL;
+  if (!stanza_queue_.empty()) {
+    result = stanza_queue_.front();
+    stanza_queue_.pop_front();
+  }
+  next_stanza_.reset(result);
+  return result;
+}
+
+XmlElement* XmppTask::MakeIq(const std::string& type,
+                             const buzz::Jid& to,
+                             const std::string& id) {
+  XmlElement* result = new XmlElement(QN_IQ);
+  if (!type.empty())
+    result->AddAttr(QN_TYPE, type);
+  if (to != JID_EMPTY)
+    result->AddAttr(QN_TO, to.Str());
+  if (!id.empty())
+    result->AddAttr(QN_ID, id);
+  return result;
+}
+
+XmlElement* XmppTask::MakeIqResult(const XmlElement * query) {
+  XmlElement* result = new XmlElement(QN_IQ);
+  result->AddAttr(QN_TYPE, STR_RESULT);
+  if (query->HasAttr(QN_FROM)) {
+    result->AddAttr(QN_TO, query->Attr(QN_FROM));
+  }
+  result->AddAttr(QN_ID, query->Attr(QN_ID));
+  return result;
+}
+
+bool XmppTask::MatchResponseIq(const XmlElement* stanza,
+                               const Jid& to,
+                               const std::string& id) {
+  if (stanza->Name() != QN_IQ)
+    return false;
+
+  if (stanza->Attr(QN_ID) != id)
+    return false;
+
+  Jid from(stanza->Attr(QN_FROM));
+  if (from == to)
+    return true;
+
+  // We address the server as "", check if we are doing so here.
+  if (to != JID_EMPTY)
+    return false;
+
+  // It is legal for the server to identify itself with "domain" or
+  // "myself@domain"
+  Jid me = client_->jid();
+  return (from == Jid(me.domain())) || (from == me.BareJid());
+}
+
+bool XmppTask::MatchRequestIq(const XmlElement* stanza,
+                              const std::string& type,
+                              const QName& qn) {
+  if (stanza->Name() != QN_IQ)
+    return false;
+
+  if (stanza->Attr(QN_TYPE) != type)
+    return false;
+
+  if (stanza->FirstNamed(qn) == NULL)
+    return false;
+
+  return true;
+}
+
+bool XmppTask::VerifyTaskRateLimit(const std::string task_name, int max_count, 
+                                   int per_x_seconds) {
+  return task_rate_manager.VerifyRateLimit(task_name, max_count, 
+                                           per_x_seconds);
+}
+
+}
diff --git a/talk/xmpp/xmpptask.h b/talk/xmpp/xmpptask.h
new file mode 100644
index 0000000..84e2fe0
--- /dev/null
+++ b/talk/xmpp/xmpptask.h
@@ -0,0 +1,130 @@
+/*
+ * 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 _XMPPTASK_H_
+#define _XMPPTASK_H_
+
+#include <string>
+#include <deque>
+#include "talk/base/sigslot.h"
+#include "talk/xmpp/xmppengine.h"
+#include "talk/base/task.h"
+
+namespace buzz {
+
+/////////////////////////////////////////////////////////////////////
+//
+// XMPPTASK
+//
+/////////////////////////////////////////////////////////////////////
+//
+// See Task and XmppClient first.
+//
+// XmppTask is a task that is designed to go underneath XmppClient and be
+// useful there.  It has a way of finding its XmppClient parent so you
+// can have it nested arbitrarily deep under an XmppClient and it can
+// still find the XMPP services.
+//
+// Tasks register themselves to listen to particular kinds of stanzas
+// that are sent out by the client.  Rather than processing stanzas
+// right away, they should decide if they own the sent stanza,
+// and if so, queue it and Wake() the task, or if a stanza does not belong
+// to you, return false right away so the next XmppTask can take a crack.
+// This technique (synchronous recognize, but asynchronous processing)
+// allows you to have arbitrary logic for recognizing stanzas yet still,
+// for example, disconnect a client while processing a stanza -
+// without reentrancy problems.
+//
+/////////////////////////////////////////////////////////////////////
+
+class XmppClient;
+
+class XmppTask :
+  public talk_base::Task,
+  public XmppStanzaHandler,
+  public sigslot::has_slots<>
+{
+ public:
+  XmppTask(talk_base::TaskParent* parent,
+           XmppEngine::HandlerLevel level = XmppEngine::HL_NONE);
+  virtual ~XmppTask();
+
+  virtual XmppClient* GetClient() const { return client_; }
+  std::string task_id() const { return id_; }
+  void set_task_id(std::string id) { id_ = id; }
+
+#ifdef _DEBUG
+  void set_debug_force_timeout(const bool f) { debug_force_timeout_ = f; }
+#endif
+
+ protected:
+  friend class XmppClient;
+
+  XmppReturnStatus SendStanza(const XmlElement* stanza);
+  XmppReturnStatus SetResult(const std::string& code);
+  XmppReturnStatus SendStanzaError(const XmlElement* element_original,
+                                   XmppStanzaError code,
+                                   const std::string& text);
+
+  virtual void Stop();
+  virtual bool HandleStanza(const XmlElement* stanza) { return false; }
+  virtual void OnDisconnect();
+  virtual int ProcessReponse() { return STATE_DONE; }
+
+  virtual void QueueStanza(const XmlElement* stanza);
+  const XmlElement* NextStanza();
+
+  bool MatchResponseIq(const XmlElement* stanza, const Jid& to,
+                       const std::string& task_id);
+
+  static bool MatchRequestIq(const XmlElement* stanza, const std::string& type,
+                             const QName& qn);
+  static XmlElement *MakeIqResult(const XmlElement* query);
+  static XmlElement *MakeIq(const std::string& type,
+                            const Jid& to, const std::string& task_id);
+
+  // Returns true if the task is under the specified rate limit and updates the
+  // rate limit accordingly
+  bool VerifyTaskRateLimit(const std::string task_name, int max_count,
+                           int per_x_seconds);
+
+private:
+  void StopImpl();
+
+  XmppClient* client_;
+  std::deque<XmlElement*> stanza_queue_;
+  talk_base::scoped_ptr<XmlElement> next_stanza_;
+  std::string id_;
+
+#ifdef _DEBUG
+  bool debug_force_timeout_;
+#endif
+};
+
+}
+
+#endif