Update to libjingle 0.6.0.

git-svn-id: http://libjingle.googlecode.com/svn/trunk@84 dd674b97-3498-5ee5-1854-bdd07cd0ff33
diff --git a/CHANGELOG b/CHANGELOG
index a6cfd9c..d5c6d59 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,9 @@
 Libjingle
 
+0.6.0 - Sep 13, 2011
+  - Add pub sub support
+  - Add unit tests
+
 0.5.9 - Aug 31, 2011
   - Add app/webrtc
   - Add webrtcvoiceengine/webrtcvideoengine
diff --git a/talk/app/webrtc/peerconnectionfactory.cc b/talk/app/webrtc/peerconnectionfactory.cc
new file mode 100644
index 0000000..0930bde
--- /dev/null
+++ b/talk/app/webrtc/peerconnectionfactory.cc
@@ -0,0 +1,83 @@
+/*
+ * libjingle
+ * Copyright 2004--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/app/webrtc/peerconnectionfactory.h"
+
+#include "talk/app/webrtc/peerconnection_proxy.h"
+#include "talk/base/logging.h"
+#include "talk/p2p/client/basicportallocator.h"
+#include "talk/session/phone/channelmanager.h"
+
+namespace webrtc {
+
+PeerConnectionFactory::PeerConnectionFactory(
+    cricket::PortAllocator* port_allocator,
+    cricket::MediaEngineInterface* media_engine,
+    cricket::DeviceManager* device_manager,
+    talk_base::Thread* worker_thread)
+    : initialized_(false),
+      port_allocator_(port_allocator),
+      channel_manager_(new cricket::ChannelManager(media_engine,
+                                                   device_manager,
+                                                   worker_thread)) {
+}
+
+PeerConnectionFactory::PeerConnectionFactory(
+    cricket::PortAllocator* port_allocator,
+    talk_base::Thread* worker_thread)
+    : initialized_(false),
+      port_allocator_(port_allocator),
+      channel_manager_(new cricket::ChannelManager(worker_thread)) {
+}
+
+PeerConnectionFactory::~PeerConnectionFactory() {
+}
+
+bool PeerConnectionFactory::Initialize() {
+  ASSERT(channel_manager_.get());
+  initialized_ = channel_manager_->Init();
+  return initialized_;
+}
+
+PeerConnection* PeerConnectionFactory::CreatePeerConnection(
+    talk_base::Thread* signaling_thread) {
+  PeerConnectionProxy* pc = NULL;
+  if (initialized_) {
+    pc =  new PeerConnectionProxy(
+        port_allocator_.get(), channel_manager_.get(), signaling_thread);
+    if (!pc->Init()) {
+      LOG(LERROR) << "Error in initializing PeerConnection";
+      delete pc;
+      pc = NULL;
+    }
+  } else {
+    LOG(LERROR) << "PeerConnectionFactory is not initialize";
+  }
+  return pc;
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/peerconnectionfactory.h b/talk/app/webrtc/peerconnectionfactory.h
new file mode 100644
index 0000000..7c65e05
--- /dev/null
+++ b/talk/app/webrtc/peerconnectionfactory.h
@@ -0,0 +1,75 @@
+/*
+ * libjingle
+ * Copyright 2004--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_APP_WEBRTC_PEERCONNECTIONFACTORY_H_
+#define TALK_APP_WEBRTC_PEERCONNECTIONFACTORY_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/scoped_ptr.h"
+
+namespace cricket {
+class ChannelManager;
+class DeviceManagerInterface;
+class MediaEngineInterface;
+class PortAllocator;
+}  // namespace cricket
+
+namespace talk_base {
+class SocketAddress;
+class Thread;
+}  // namespace talk_base
+
+namespace webrtc {
+
+class PeerConnection;
+
+class PeerConnectionFactory {
+ public:
+  PeerConnectionFactory(cricket::PortAllocator* port_allocator,
+                        cricket::MediaEngineInterface* media_engine,
+                        cricket::DeviceManagerInterface* device_manager,
+                        talk_base::Thread* worker_thread);
+  PeerConnectionFactory(cricket::PortAllocator* port_allocator,
+                        talk_base::Thread* worker_thread);
+
+  virtual ~PeerConnectionFactory();
+  bool Initialize();
+
+  PeerConnection* CreatePeerConnection(talk_base::Thread* signaling_thread);
+
+ private:
+  bool initialized_;
+  talk_base::scoped_ptr<cricket::PortAllocator> port_allocator_;
+  talk_base::scoped_ptr<cricket::ChannelManager> channel_manager_;
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_PEERCONNECTIONFACTORY_H_
+
diff --git a/talk/base/asynchttprequest_unittest.cc b/talk/base/asynchttprequest_unittest.cc
new file mode 100644
index 0000000..e8db1e0
--- /dev/null
+++ b/talk/base/asynchttprequest_unittest.cc
@@ -0,0 +1,191 @@
+/*
+ * libjingle
+ * Copyright 2004--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 <string>
+#include "talk/base/asynchttprequest.h"
+#include "talk/base/gunit.h"
+#include "talk/base/httpserver.h"
+#include "talk/base/socketstream.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+static const SocketAddress kServerAddr("127.0.0.1", 0);
+static const SocketAddress kServerHostnameAddr("localhost", 0);
+static const char kServerGetPath[] = "/get";
+static const char kServerPostPath[] = "/post";
+static const char kServerResponse[] = "This is a test";
+  
+class TestHttpServer : public HttpServer, public sigslot::has_slots<> {
+ public:
+  TestHttpServer(Thread* thread, const SocketAddress& addr)
+      : socket_(thread->socketserver()->CreateAsyncSocket(SOCK_STREAM)) {
+    socket_->Bind(addr);
+    socket_->Listen(5);
+    socket_->SignalReadEvent.connect(this, &TestHttpServer::OnAccept);
+  }
+
+  SocketAddress address() const { return socket_->GetLocalAddress(); }
+
+ private:
+  void OnAccept(AsyncSocket* socket) {
+    AsyncSocket* new_socket = socket_->Accept(NULL);
+    if (new_socket) {
+      HandleConnection(new SocketStream(new_socket));
+    }
+  }
+  talk_base::scoped_ptr<AsyncSocket> socket_;
+};
+
+class AsyncHttpRequestTest : public testing::Test,
+                             public sigslot::has_slots<> {
+ public:
+  AsyncHttpRequestTest()
+      : done_(false), server_(Thread::Current(), kServerAddr) {
+    server_.SignalHttpRequest.connect(this, &AsyncHttpRequestTest::OnRequest);
+  }
+
+  bool done() const { return done_; }
+
+  AsyncHttpRequest* CreateGetRequest(const std::string& host, int port,
+                                     const std::string& path) {
+    talk_base::AsyncHttpRequest* request =
+        new talk_base::AsyncHttpRequest("unittest");
+    request->SignalWorkDone.connect(this,
+        &AsyncHttpRequestTest::OnRequestDone);
+    request->request().verb = talk_base::HV_GET;
+    request->set_host(host);
+    request->set_port(port);
+    request->request().path = path;
+    request->response().document.reset(new MemoryStream());
+    return request;
+  }
+  AsyncHttpRequest* CreatePostRequest(const std::string& host, int port,
+                                      const std::string& path,
+                                      const std::string content_type,
+                                      StreamInterface* content) {
+    talk_base::AsyncHttpRequest* request =
+        new talk_base::AsyncHttpRequest("unittest");
+    request->SignalWorkDone.connect(this,
+        &AsyncHttpRequestTest::OnRequestDone);
+    request->request().verb = talk_base::HV_POST;
+    request->set_host(host);
+    request->set_port(port);
+    request->request().path = path;
+    request->request().setContent(content_type, content);
+    request->response().document.reset(new MemoryStream());
+    return request;
+  }
+
+  const TestHttpServer& server() const { return server_; }
+
+ protected:
+  void OnRequest(HttpServer* server, HttpServerTransaction* t) {
+    if (t->request.path == kServerGetPath) {
+      t->response.set_success("text/plain", new MemoryStream(kServerResponse));
+    } else if (t->request.path == kServerPostPath) {
+      // reverse the data and reply
+      size_t size;
+      StreamInterface* in = t->request.document.get();
+      StreamInterface* out = new MemoryStream();
+      in->GetSize(&size);
+      for (size_t i = 0; i < size; ++i) {
+        char ch;
+        in->SetPosition(size - i - 1);
+        in->Read(&ch, 1, NULL, NULL);
+        out->Write(&ch, 1, NULL, NULL);
+      }
+      out->Rewind();
+      t->response.set_success("text/plain", out);
+    } else {
+      t->response.set_error(404);
+    }
+    server_.Respond(t);
+  }
+  void OnRequestDone(SignalThread* thread) {
+    done_ = true;
+  }
+
+ private:
+  bool done_;
+  TestHttpServer server_;
+};
+
+TEST_F(AsyncHttpRequestTest, TestGetSuccess) {
+  AsyncHttpRequest* req = CreateGetRequest(
+      kServerHostnameAddr.hostname(), server().address().port(),
+      kServerGetPath);
+  req->Start();
+  EXPECT_TRUE_WAIT(done(), 5000);
+  std::string response;
+  EXPECT_EQ(200U, req->response().scode);
+  ASSERT_TRUE(req->response().document.get() != NULL);
+  req->response().document->Rewind();
+  req->response().document->ReadLine(&response);
+  EXPECT_EQ(kServerResponse, response);
+  req->Release();
+}
+
+TEST_F(AsyncHttpRequestTest, TestGetNotFound) {
+  AsyncHttpRequest* req = CreateGetRequest(
+      kServerHostnameAddr.hostname(), server().address().port(),
+      "/bad");
+  req->Start();
+  EXPECT_TRUE_WAIT(done(), 5000);
+  size_t size;
+  EXPECT_EQ(404U, req->response().scode);
+  ASSERT_TRUE(req->response().document.get() != NULL);
+  req->response().document->GetSize(&size);
+  EXPECT_EQ(0U, size);
+  req->Release();
+}
+
+TEST_F(AsyncHttpRequestTest, TestPostSuccess) {
+  AsyncHttpRequest* req = CreatePostRequest(
+      kServerHostnameAddr.hostname(), server().address().port(),
+      kServerPostPath, "text/plain", new MemoryStream("abcd1234"));
+  req->Start();
+  EXPECT_TRUE_WAIT(done(), 5000);
+  std::string response;
+  EXPECT_EQ(200U, req->response().scode);
+  ASSERT_TRUE(req->response().document.get() != NULL);
+  req->response().document->Rewind();
+  req->response().document->ReadLine(&response);
+  EXPECT_EQ("4321dcba", response);
+  req->Release();
+}
+
+// Ensure that we shut down properly even if work is outstanding.
+TEST_F(AsyncHttpRequestTest, TestCancel) {
+  AsyncHttpRequest* req = CreateGetRequest(
+      kServerHostnameAddr.hostname(), server().address().port(),
+      kServerGetPath);
+  req->Start();
+  req->Destroy(true);
+}
+
+}  // namespace talk_base
diff --git a/talk/base/autodetectproxy_unittest.cc b/talk/base/autodetectproxy_unittest.cc
new file mode 100644
index 0000000..bdf2786
--- /dev/null
+++ b/talk/base/autodetectproxy_unittest.cc
@@ -0,0 +1,100 @@
+/*
+ * libjingle
+ * Copyright 2004--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/autodetectproxy.h"
+#include "talk/base/gunit.h"
+#include "talk/base/httpcommon.h"
+#include "talk/base/httpcommon-inl.h"
+
+namespace talk_base {
+
+static const char kUserAgent[] = "";
+static const char kPath[] = "/";
+static const char kHost[] = "relay.google.com";
+static const uint16 kPort = 443;
+static const bool kSecure = true;
+// Each of the two stages in AutoDetectProxy has a 2-second time-out, so 5
+// seconds total should be enough.
+static const int kTimeoutMs = 5000;
+
+class AutoDetectProxyTest : public testing::Test, public sigslot::has_slots<> {
+ public:
+  AutoDetectProxyTest() : auto_detect_proxy_(NULL), done_(false) {}
+
+ protected:
+  bool Create(const std::string &user_agent,
+              const std::string &path,
+              const std::string &host,
+              uint16 port,
+              bool secure) {
+    auto_detect_proxy_ = new AutoDetectProxy(user_agent);
+    EXPECT_TRUE(auto_detect_proxy_ != NULL);
+    if (!auto_detect_proxy_) {
+      return false;
+    }
+    Url<char> host_url(path, host, port);
+    host_url.set_secure(secure);
+    auto_detect_proxy_->set_server_url(host_url.url());
+    auto_detect_proxy_->SignalWorkDone.connect(
+        this,
+        &AutoDetectProxyTest::OnWorkDone);
+    auto_detect_proxy_->Start();
+    return true;
+  }
+
+  bool Run(int timeout_ms) {
+    EXPECT_TRUE_WAIT(done_, timeout_ms);
+    return done_;
+  }
+
+ private:
+  void OnWorkDone(talk_base::SignalThread *thread) {
+    AutoDetectProxy *auto_detect_proxy =
+        static_cast<talk_base::AutoDetectProxy *>(thread);
+    EXPECT_TRUE(auto_detect_proxy == auto_detect_proxy_);
+    auto_detect_proxy_ = NULL;
+    auto_detect_proxy->Release();
+    done_ = true;
+  }
+
+  AutoDetectProxy *auto_detect_proxy_;
+  bool done_;
+};
+
+// Test that proxy detection completes successfully. (Does not actually verify
+// the correct detection result since we don't know what proxy to expect on an
+// arbitrary machine.)
+TEST_F(AutoDetectProxyTest, TestProxyDetection) {
+  ASSERT_TRUE(Create(kUserAgent,
+                     kPath,
+                     kHost,
+                     kPort,
+                     kSecure));
+  ASSERT_TRUE(Run(kTimeoutMs));
+}
+
+}  // namespace talk_base
diff --git a/talk/base/bytebuffer_unittest.cc b/talk/base/bytebuffer_unittest.cc
new file mode 100644
index 0000000..2970fdc
--- /dev/null
+++ b/talk/base/bytebuffer_unittest.cc
@@ -0,0 +1,185 @@
+/*
+ * libjingle
+ * Copyright 2004--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/bytebuffer.h"
+#include "talk/base/byteorder.h"
+#include "talk/base/common.h"
+#include "talk/base/gunit.h"
+
+namespace talk_base {
+
+TEST(ByteBufferTest, TestByteOrder) {
+  uint16 n16 = 1;
+  uint32 n32 = 1;
+  uint64 n64 = 1;
+
+  EXPECT_EQ(n16, NetworkToHost16(HostToNetwork16(n16)));
+  EXPECT_EQ(n32, NetworkToHost32(HostToNetwork32(n32)));
+  EXPECT_EQ(n64, NetworkToHost64(HostToNetwork64(n64)));
+
+  if (IsHostBigEndian()) {
+    // The host is the network (big) endian.
+    EXPECT_EQ(n16, HostToNetwork16(n16));
+    EXPECT_EQ(n32, HostToNetwork32(n32));
+    EXPECT_EQ(n64, HostToNetwork64(n64));
+
+    // GetBE converts big endian to little endian here.
+    EXPECT_EQ(n16 >> 8, GetBE16(&n16));
+    EXPECT_EQ(n32 >> 24, GetBE32(&n32));
+    EXPECT_EQ(n64 >> 56, GetBE64(&n64));
+  } else {
+    // The host is little endian.
+    EXPECT_NE(n16, HostToNetwork16(n16));
+    EXPECT_NE(n32, HostToNetwork32(n32));
+    EXPECT_NE(n64, HostToNetwork64(n64));
+
+    // GetBE converts little endian to big endian here.
+    EXPECT_EQ(GetBE16(&n16), HostToNetwork16(n16));
+    EXPECT_EQ(GetBE32(&n32), HostToNetwork32(n32));
+    EXPECT_EQ(GetBE64(&n64), HostToNetwork64(n64));
+
+    // GetBE converts little endian to big endian here.
+    EXPECT_EQ(n16 << 8, GetBE16(&n16));
+    EXPECT_EQ(n32 << 24, GetBE32(&n32));
+    EXPECT_EQ(n64 << 56, GetBE64(&n64));
+  }
+}
+
+TEST(ByteBufferTest, TestBufferLength) {
+  ByteBuffer buffer;
+  size_t size = 0;
+  EXPECT_EQ(size, buffer.Length());
+
+  buffer.WriteUInt8(1);
+  ++size;
+  EXPECT_EQ(size, buffer.Length());
+
+  buffer.WriteUInt16(1);
+  size += 2;
+  EXPECT_EQ(size, buffer.Length());
+
+  buffer.WriteUInt24(1);
+  size += 3;
+  EXPECT_EQ(size, buffer.Length());
+
+  buffer.WriteUInt32(1);
+  size += 4;
+  EXPECT_EQ(size, buffer.Length());
+
+  buffer.WriteUInt64(1);
+  size += 8;
+  EXPECT_EQ(size, buffer.Length());
+
+  EXPECT_TRUE(buffer.Consume(0));
+  EXPECT_EQ(size, buffer.Length());
+
+  EXPECT_TRUE(buffer.Consume(4));
+  size -= 4;
+  EXPECT_EQ(size, buffer.Length());
+
+  EXPECT_TRUE(buffer.Shift(4));
+  size -= 4;
+  EXPECT_EQ(size, buffer.Length());
+}
+
+TEST(ByteBufferTest, TestReadWriteBuffer) {
+  ByteBuffer::ByteOrder orders[2] = { ByteBuffer::ORDER_HOST,
+                                      ByteBuffer::ORDER_NETWORK };
+  for (size_t i = 0; i < ARRAY_SIZE(orders); i++) {
+    ByteBuffer buffer(orders[i]);
+    uint8 ru8;
+    EXPECT_FALSE(buffer.ReadUInt8(&ru8));
+
+    // Write and read uint8.
+    uint8 wu8 = 1;
+    buffer.WriteUInt8(wu8);
+    EXPECT_TRUE(buffer.ReadUInt8(&ru8));
+    EXPECT_EQ(wu8, ru8);
+    EXPECT_EQ(0U, buffer.Length());
+
+    // Write and read uint16.
+    uint16 wu16 = (1 << 8) + 1;
+    buffer.WriteUInt16(wu16);
+    uint16 ru16;
+    EXPECT_TRUE(buffer.ReadUInt16(&ru16));
+    EXPECT_EQ(wu16, ru16);
+    EXPECT_EQ(0U, buffer.Length());
+
+    // Write and read uint24.
+    uint32 wu24 = (3 << 16) + (2 << 8) + 1;
+    buffer.WriteUInt24(wu24);
+    uint32 ru24;
+    EXPECT_TRUE(buffer.ReadUInt24(&ru24));
+    EXPECT_EQ(wu24, ru24);
+    EXPECT_EQ(0U, buffer.Length());
+
+    // Write and read uint32.
+    uint32 wu32 = (4 << 24) + (3 << 16) + (2 << 8) + 1;
+    buffer.WriteUInt32(wu32);
+    uint32 ru32;
+    EXPECT_TRUE(buffer.ReadUInt32(&ru32));
+    EXPECT_EQ(wu32, ru32);
+    EXPECT_EQ(0U, buffer.Length());
+
+    // Write and read uint64.
+    uint32 another32 = (8 << 24) + (7 << 16) + (6 << 8) + 5;
+    uint64 wu64 = (static_cast<uint64>(another32) << 32) + wu32;
+    buffer.WriteUInt64(wu64);
+    uint64 ru64;
+    EXPECT_TRUE(buffer.ReadUInt64(&ru64));
+    EXPECT_EQ(wu64, ru64);
+    EXPECT_EQ(0U, buffer.Length());
+
+    // Write and read string.
+    std::string write_string("hello");
+    buffer.WriteString(write_string);
+    std::string read_string;
+    EXPECT_TRUE(buffer.ReadString(&read_string, write_string.size()));
+    EXPECT_EQ(write_string, read_string);
+    EXPECT_EQ(0U, buffer.Length());
+
+    // Write and read in order.
+    buffer.WriteUInt8(wu8);
+    buffer.WriteUInt16(wu16);
+    buffer.WriteUInt24(wu24);
+    buffer.WriteUInt32(wu32);
+    buffer.WriteUInt64(wu64);
+    EXPECT_TRUE(buffer.ReadUInt8(&ru8));
+    EXPECT_EQ(wu8, ru8);
+    EXPECT_TRUE(buffer.ReadUInt16(&ru16));
+    EXPECT_EQ(wu16, ru16);
+    EXPECT_TRUE(buffer.ReadUInt24(&ru24));
+    EXPECT_EQ(wu24, ru24);
+    EXPECT_TRUE(buffer.ReadUInt32(&ru32));
+    EXPECT_EQ(wu32, ru32);
+    EXPECT_TRUE(buffer.ReadUInt64(&ru64));
+    EXPECT_EQ(wu64, ru64);
+    EXPECT_EQ(0U, buffer.Length());
+  }
+}
+
+}  // namespace talk_base
diff --git a/talk/base/event_unittest.cc b/talk/base/event_unittest.cc
new file mode 100644
index 0000000..5a3c1c6
--- /dev/null
+++ b/talk/base/event_unittest.cc
@@ -0,0 +1,59 @@
+/*
+ * libjingle
+ * Copyright 2004--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/event.h"
+#include "talk/base/gunit.h"
+
+namespace talk_base {
+
+TEST(EventTest, InitiallySignaled) {
+  Event event(false, true);
+  ASSERT_TRUE(event.Wait(0));
+}
+
+TEST(EventTest, ManualReset) {
+  Event event(true, false);
+  ASSERT_FALSE(event.Wait(0));
+
+  event.Set();
+  ASSERT_TRUE(event.Wait(0));
+  ASSERT_TRUE(event.Wait(0));
+
+  event.Reset();
+  ASSERT_FALSE(event.Wait(0));
+}
+
+TEST(EventTest, AutoReset) {
+  Event event(false, false);
+  ASSERT_FALSE(event.Wait(0));
+
+  event.Set();
+  ASSERT_TRUE(event.Wait(0));
+  ASSERT_FALSE(event.Wait(0));
+}
+
+}  // namespace talk_base
diff --git a/talk/base/fileutils_mock.h b/talk/base/fileutils_mock.h
new file mode 100644
index 0000000..2ccb6a3
--- /dev/null
+++ b/talk/base/fileutils_mock.h
@@ -0,0 +1,270 @@
+/*
+ * libjingle
+ * Copyright 2004--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_FILEUTILS_MOCK_H_
+#define TALK_BASE_FILEUTILS_MOCK_H_
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "talk/base/fileutils.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/stream.h"
+#include "testing/base/public/gmock.h"
+
+namespace talk_base {
+
+class FakeFileStream : public FileStream {
+  public:
+    explicit FakeFileStream(const std::string & contents) :
+      string_stream_(contents)
+    {}
+
+    virtual StreamResult Read(void* buffer, size_t buffer_len,
+                              size_t* read, int* error) {
+      return string_stream_.Read(buffer, buffer_len, read, error);
+    }
+
+    virtual void Close() {
+      return string_stream_.Close();
+    }
+    virtual bool GetSize(size_t* size) const {
+      return string_stream_.GetSize(size);
+    }
+
+  private:
+    StringStream string_stream_;
+};
+
+class FakeDirectoryIterator : public DirectoryIterator {
+  public:
+    typedef std::pair<std::string, std::string> File;
+
+    /*
+     * files should be sorted by directory
+     * put '/' at the end of file if you want it to be a directory
+     *
+     * Sample list:
+     *  /var/dir/file1
+     *  /var/dir/file2
+     *  /var/dir/subdir1/
+     *  /var/dir/subdir2/
+     *  /var/dir2/file2
+     *  /var/dir3/
+     *
+     *  you can call Iterate for any path: /var, /var/dir, /var/dir2
+     *  unrelated files will be ignored
+     */
+    explicit FakeDirectoryIterator(const std::vector<File>& all_files) :
+      all_files_(all_files) {}
+
+    virtual bool Iterate(const Pathname& path) {
+      path_iterator_ = all_files_.begin();
+      path_ = path.pathname();
+
+      // make sure path ends end with '/'
+      if (path_.rfind(Pathname::DefaultFolderDelimiter()) != path_.size() - 1)
+        path_ += Pathname::DefaultFolderDelimiter();
+
+      return  FakeDirectoryIterator::Search(std::string(""));
+    }
+
+    virtual bool Next() {
+      std::string current_name = Name();
+      path_iterator_++;
+      return FakeDirectoryIterator::Search(current_name);
+    }
+
+    bool Search(const std::string& current_name) {
+      for (; path_iterator_ != all_files_.end(); path_iterator_++) {
+        if (path_iterator_->first.find(path_) == 0
+            && Name().compare(current_name) != 0) {
+          return true;
+        }
+      }
+
+      return false;
+    }
+
+    virtual bool IsDirectory() const {
+      std::string sub_path = path_iterator_->first;
+
+      return std::string::npos !=
+        sub_path.find(Pathname::DefaultFolderDelimiter(), path_.size());
+    }
+
+    virtual std::string Name() const {
+      std::string sub_path = path_iterator_->first;
+
+      // path     - top level path  (ex. /var/lib)
+      // sub_path - subpath under top level path (ex. /var/lib/dir/dir/file )
+      // find shortest non-trivial common path. (ex. /var/lib/dir)
+      size_t start  = path_.size();
+      size_t end    = sub_path.find(Pathname::DefaultFolderDelimiter(), start);
+
+      if (end != std::string::npos) {
+        return sub_path.substr(start, end - start);
+      } else {
+        return sub_path.substr(start);
+      }
+    }
+
+  private:
+    const std::vector<File> all_files_;
+
+    std::string path_;
+    std::vector<File>::const_iterator path_iterator_;
+};
+
+class FakeFileSystem : public FilesystemInterface {
+  public:
+    typedef std::pair<std::string, std::string> File;
+
+    explicit FakeFileSystem(const std::vector<File>& all_files) :
+     all_files_(all_files) {}
+
+    virtual DirectoryIterator *IterateDirectory() {
+     return new FakeDirectoryIterator(all_files_);
+    }
+
+    virtual FileStream * OpenFile(
+       const Pathname &filename,
+       const std::string &mode) {
+     std::vector<File>::const_iterator i_files = all_files_.begin();
+     std::string path = filename.pathname();
+
+     for (; i_files != all_files_.end(); i_files++) {
+       if (i_files->first.compare(path) == 0) {
+         return new FakeFileStream(i_files->second);
+       }
+     }
+
+     return NULL;
+    }
+
+    bool CreatePrivateFile(const Pathname &filename) {
+      EXPECT_TRUE_M(false, "Unsupported operation");
+      return false;
+    }
+    bool DeleteFile(const Pathname &filename) {
+      EXPECT_TRUE_M(false, "Unsupported operation");
+      return false;
+    }
+    bool DeleteEmptyFolder(const Pathname &folder) {
+      EXPECT_TRUE_M(false, "Unsupported operation");
+      return false;
+    }
+    bool DeleteFolderContents(const Pathname &folder) {
+      EXPECT_TRUE_M(false, "Unsupported operation");
+      return false;
+    }
+    bool DeleteFolderAndContents(const Pathname &folder) {
+      EXPECT_TRUE_M(false, "Unsupported operation");
+      return false;
+    }
+    bool CreateFolder(const Pathname &pathname) {
+      EXPECT_TRUE_M(false, "Unsupported operation");
+      return false;
+    }
+    bool MoveFolder(const Pathname &old_path, const Pathname &new_path) {
+      EXPECT_TRUE_M(false, "Unsupported operation");
+      return false;
+    }
+    bool MoveFile(const Pathname &old_path, const Pathname &new_path) {
+      EXPECT_TRUE_M(false, "Unsupported operation");
+      return false;
+    }
+    bool CopyFile(const Pathname &old_path, const Pathname &new_path) {
+      EXPECT_TRUE_M(false, "Unsupported operation");
+      return false;
+    }
+    bool IsFolder(const Pathname &pathname) {
+      EXPECT_TRUE_M(false, "Unsupported operation");
+      return false;
+    }
+    bool IsFile(const Pathname &pathname) {
+      EXPECT_TRUE_M(false, "Unsupported operation");
+      return false;
+    }
+    bool IsAbsent(const Pathname &pathname) {
+      EXPECT_TRUE_M(false, "Unsupported operation");
+      return false;
+    }
+    bool IsTemporaryPath(const Pathname &pathname) {
+      EXPECT_TRUE_M(false, "Unsupported operation");
+      return false;
+    }
+    bool GetTemporaryFolder(Pathname &path, bool create,
+                            const std::string *append) {
+      EXPECT_TRUE_M(false, "Unsupported operation");
+      return false;
+    }
+    std::string TempFilename(const Pathname &dir, const std::string &prefix) {
+      EXPECT_TRUE_M(false, "Unsupported operation");
+      return std::string();
+    }
+    bool GetFileSize(const Pathname &path, size_t *size) {
+      EXPECT_TRUE_M(false, "Unsupported operation");
+      return false;
+    }
+    bool GetFileTime(const Pathname &path, FileTimeType which,
+                     time_t* time) {
+      EXPECT_TRUE_M(false, "Unsupported operation");
+      return false;
+    }
+    bool GetAppPathname(Pathname *path) {
+      EXPECT_TRUE_M(false, "Unsupported operation");
+      return false;
+    }
+    bool GetAppDataFolder(Pathname *path, bool per_user) {
+      EXPECT_TRUE_M(per_user, "Unsupported operation");
+#ifdef WIN32
+      path->SetPathname("c:\\Users\\test_user", "");
+#else
+      path->SetPathname("/home/user/test_user", "");
+#endif
+      return true;
+    }
+    bool GetAppTempFolder(Pathname *path) {
+      EXPECT_TRUE_M(false, "Unsupported operation");
+      return false;
+    }
+    bool GetDiskFreeSpace(const Pathname &path, int64 *freebytes) {
+      EXPECT_TRUE_M(false, "Unsupported operation");
+      return false;
+    }
+    Pathname GetCurrentDirectory() {
+      return Pathname();
+    }
+
+  private:
+    const std::vector<File> all_files_;
+};
+}  // namespace talk_base
+
+#endif  // TALK_BASE_FILEUTILS_MOCK_H_
diff --git a/talk/base/fileutils_unittest.cc b/talk/base/fileutils_unittest.cc
new file mode 100644
index 0000000..992ea82
--- /dev/null
+++ b/talk/base/fileutils_unittest.cc
@@ -0,0 +1,148 @@
+/*
+ * libjingle
+ * Copyright 2004--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/fileutils.h"
+#include "talk/base/gunit.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/stream.h"
+
+namespace talk_base {
+
+// Make sure we can get a temp folder for the later tests.
+TEST(FilesystemTest, GetTemporaryFolder) {
+  Pathname path;
+  EXPECT_TRUE(Filesystem::GetTemporaryFolder(path, true, NULL));
+}
+
+// Test creating a temp file, reading it back in, and deleting it.
+TEST(FilesystemTest, TestOpenFile) {
+  Pathname path;
+  EXPECT_TRUE(Filesystem::GetTemporaryFolder(path, true, NULL));
+  path.SetPathname(Filesystem::TempFilename(path, "ut"));
+
+  FileStream* fs;
+  char buf[256];
+  size_t bytes;
+
+  fs = Filesystem::OpenFile(path, "wb");
+  ASSERT_TRUE(fs != NULL);
+  EXPECT_EQ(SR_SUCCESS, fs->Write("test", 4, &bytes, NULL));
+  EXPECT_EQ(4U, bytes);
+  delete fs;
+
+  EXPECT_TRUE(Filesystem::IsFile(path));
+
+  fs = Filesystem::OpenFile(path, "rb");
+  ASSERT_TRUE(fs != NULL);
+  EXPECT_EQ(SR_SUCCESS, fs->Read(buf, sizeof(buf), &bytes, NULL));
+  EXPECT_EQ(4U, bytes);
+  delete fs;
+
+  EXPECT_TRUE(Filesystem::DeleteFile(path));
+  EXPECT_FALSE(Filesystem::IsFile(path));
+}
+
+// Test opening a non-existent file.
+TEST(FilesystemTest, TestOpenBadFile) {
+  Pathname path;
+  EXPECT_TRUE(Filesystem::GetTemporaryFolder(path, true, NULL));
+  path.SetFilename("not an actual file");
+
+  EXPECT_FALSE(Filesystem::IsFile(path));
+
+  FileStream* fs = Filesystem::OpenFile(path, "rb");
+  EXPECT_FALSE(fs != NULL);
+}
+
+// Test that CreatePrivateFile fails for existing files and succeeds for
+// non-existent ones.
+TEST(FilesystemTest, TestCreatePrivateFile) {
+  Pathname path;
+  EXPECT_TRUE(Filesystem::GetTemporaryFolder(path, true, NULL));
+  path.SetFilename("private_file_test");
+
+  // First call should succeed because the file doesn't exist yet.
+  EXPECT_TRUE(Filesystem::CreatePrivateFile(path));
+  // Next call should fail, because now it exists.
+  EXPECT_FALSE(Filesystem::CreatePrivateFile(path));
+
+  // Verify that we have permission to open the file for reading and writing.
+  scoped_ptr<FileStream> fs(Filesystem::OpenFile(path, "wb"));
+  EXPECT_TRUE(fs.get() != NULL);
+  // Have to close the file on Windows before it will let us delete it.
+  fs.reset();
+
+  // Verify that we have permission to delete the file.
+  EXPECT_TRUE(Filesystem::DeleteFile(path));
+}
+
+// Test checking for free disk space.
+TEST(FilesystemTest, TestGetDiskFreeSpace) {
+  // Note that we should avoid picking any file/folder which could be located
+  // at the remotely mounted drive/device.
+  Pathname path;
+  ASSERT_TRUE(Filesystem::GetAppDataFolder(&path, true));
+
+  int64 free1 = 0;
+  EXPECT_TRUE(Filesystem::IsFolder(path));
+  EXPECT_FALSE(Filesystem::IsFile(path));
+  EXPECT_TRUE(Filesystem::GetDiskFreeSpace(path, &free1));
+  EXPECT_GT(free1, 0);
+
+  int64 free2 = 0;
+  path.AppendFolder("this_folder_doesnt_exist");
+  EXPECT_FALSE(Filesystem::IsFolder(path));
+  EXPECT_TRUE(Filesystem::IsAbsent(path));
+  EXPECT_TRUE(Filesystem::GetDiskFreeSpace(path, &free2));
+  // These should be the same disk, and disk free space should not have changed
+  // by more than 1% between the two calls.
+  EXPECT_LT(static_cast<int64>(free1 * .9), free2);
+  EXPECT_LT(free2, static_cast<int64>(free1 * 1.1));
+
+  int64 free3 = 0;
+  path.clear();
+  EXPECT_TRUE(path.empty());
+  EXPECT_TRUE(Filesystem::GetDiskFreeSpace(path, &free3));
+  // Current working directory may not be where exe is.
+  // EXPECT_LT(static_cast<int64>(free1 * .9), free3);
+  // EXPECT_LT(free3, static_cast<int64>(free1 * 1.1));
+  EXPECT_GT(free3, 0);
+}
+
+// Tests that GetCurrentDirectory() returns something.
+TEST(FilesystemTest, TestGetCurrentDirectory) {
+  EXPECT_FALSE(Filesystem::GetCurrentDirectory().empty());
+}
+
+// Tests that GetAppPathname returns something.
+TEST(FilesystemTest, TestGetAppPathname) {
+  Pathname path;
+  EXPECT_TRUE(Filesystem::GetAppPathname(&path));
+  EXPECT_FALSE(path.empty());
+}
+
+}  // namespace talk_base
diff --git a/talk/base/helpers_unittest.cc b/talk/base/helpers_unittest.cc
new file mode 100644
index 0000000..0fe1d5b
--- /dev/null
+++ b/talk/base/helpers_unittest.cc
@@ -0,0 +1,83 @@
+/*
+ * libjingle
+ * Copyright 2004--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 <string>
+
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+
+namespace talk_base {
+
+TEST(RandomTest, TestCreateRandomId) {
+  CreateRandomId();
+}
+
+TEST(RandomTest, TestCreateRandomDouble) {
+  for (int i = 0; i < 100; ++i) {
+    double r = CreateRandomDouble();
+    EXPECT_GE(r, 0.0);
+    EXPECT_LT(r, 1.0);
+  }
+}
+
+TEST(RandomTest, TestCreateNonZeroRandomId) {
+  EXPECT_NE(0U, CreateRandomNonZeroId());
+}
+
+TEST(RandomTest, TestCreateRandomString) {
+  std::string random = CreateRandomString(256);
+  EXPECT_EQ(256U, random.size());
+  std::string random2;
+  EXPECT_TRUE(CreateRandomString(256, &random2));
+  EXPECT_NE(random, random2);
+  EXPECT_EQ(256U, random2.size());
+}
+
+TEST(RandomTest, TestCreateRandomForTest) {
+  // Make sure we get the output we expect.
+  SetRandomTestMode(true);
+  EXPECT_EQ(2154761789U, CreateRandomId());
+  EXPECT_EQ("h0ISP4S5SJKH/9EY", CreateRandomString(16));
+
+  // Reset and make sure we get the same output.
+  SetRandomTestMode(true);
+  EXPECT_EQ(2154761789U, CreateRandomId());
+  EXPECT_EQ("h0ISP4S5SJKH/9EY", CreateRandomString(16));
+
+  // Test different character sets.
+  SetRandomTestMode(true);
+  std::string str;
+  EXPECT_TRUE(CreateRandomString(16, "a", &str));
+  EXPECT_EQ("aaaaaaaaaaaaaaaa", str);
+  EXPECT_TRUE(CreateRandomString(16, "abc", &str));
+  EXPECT_EQ("acbccaaaabbaacbb", str);
+
+  // Turn off test mode for other tests.
+  SetRandomTestMode(false);
+}
+
+}  // namespace talk_base
diff --git a/talk/base/host_unittest.cc b/talk/base/host_unittest.cc
new file mode 100644
index 0000000..aba87af
--- /dev/null
+++ b/talk/base/host_unittest.cc
@@ -0,0 +1,33 @@
+/*
+ * libjingle
+ * Copyright 2004--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/gunit.h"
+#include "talk/base/host.h"
+
+TEST(Host, GetHostName) {
+  EXPECT_NE("", talk_base::GetHostName());
+}
diff --git a/talk/base/httpbase_unittest.cc b/talk/base/httpbase_unittest.cc
new file mode 100644
index 0000000..73ef949
--- /dev/null
+++ b/talk/base/httpbase_unittest.cc
@@ -0,0 +1,536 @@
+/*
+ * libjingle
+ * Copyright 2004--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/gunit.h"
+#include "talk/base/httpbase.h"
+#include "talk/base/testutils.h"
+
+namespace talk_base {
+
+const char* const kHttpResponse =
+  "HTTP/1.1 200\r\n"
+  "Connection: Keep-Alive\r\n"
+  "Content-Type: text/plain\r\n"
+  "Proxy-Authorization: 42\r\n"
+  "Transfer-Encoding: chunked\r\n"
+  "\r\n"
+  "00000008\r\n"
+  "Goodbye!\r\n"
+  "0\r\n\r\n";
+
+const char* const kHttpEmptyResponse =
+  "HTTP/1.1 200\r\n"
+  "Connection: Keep-Alive\r\n"
+  "Content-Length: 0\r\n"
+  "Proxy-Authorization: 42\r\n"
+  "\r\n";
+
+const char* const kHttpResponsePrefix =
+  "HTTP/1.1 200\r\n"
+  "Connection: Keep-Alive\r\n"
+  "Content-Type: text/plain\r\n"
+  "Proxy-Authorization: 42\r\n"
+  "Transfer-Encoding: chunked\r\n"
+  "\r\n"
+  "8\r\n"
+  "Goodbye!\r\n";
+
+class HttpBaseTest : public testing::Test, public IHttpNotify {
+public:
+  enum EventType { E_HEADER_COMPLETE, E_COMPLETE, E_CLOSED };
+  struct Event {
+    EventType event;
+    bool chunked;
+    size_t data_size;
+    HttpMode mode;
+    HttpError err;
+  };
+  HttpBaseTest() : mem(NULL), obtain_stream(false), http_stream(NULL) { }
+
+  virtual void SetUp() { }
+  virtual void TearDown() {
+    // Avoid an ASSERT, in case a test doesn't clean up properly
+    base.abort(HE_NONE);
+  }
+
+  virtual HttpError onHttpHeaderComplete(bool chunked, size_t& data_size) {
+    LOG_F(LS_VERBOSE) << "chunked: " << chunked << " size: " << data_size;
+    Event e = { E_HEADER_COMPLETE, chunked, data_size, HM_NONE, HE_NONE};
+    events.push_back(e);
+    if (obtain_stream) {
+      ObtainDocumentStream();
+    }
+    return HE_NONE;
+  }
+  virtual void onHttpComplete(HttpMode mode, HttpError err) {
+    LOG_F(LS_VERBOSE) << "mode: " << mode << " err: " << err;
+    Event e = { E_COMPLETE, false, 0, mode, err };
+    events.push_back(e);
+  }
+  virtual void onHttpClosed(HttpError err) {
+    LOG_F(LS_VERBOSE) << "err: " << err;
+    Event e = { E_CLOSED, false, 0, HM_NONE, err };
+    events.push_back(e);
+  }
+
+  void SetupSource(const char* response);
+
+  void VerifyHeaderComplete(size_t event_count, bool empty_doc);
+  void VerifyDocumentContents(const char* expected_data,
+                              size_t expected_length = SIZE_UNKNOWN);
+
+  void ObtainDocumentStream();
+  void VerifyDocumentStreamIsOpening();
+  void VerifyDocumentStreamOpenEvent();
+  void ReadDocumentStreamData(const char* expected_data);
+  void VerifyDocumentStreamIsEOS();
+
+  void SetupDocument(const char* response);
+  void VerifySourceContents(const char* expected_data,
+                            size_t expected_length = SIZE_UNKNOWN);
+
+  void VerifyTransferComplete(HttpMode mode, HttpError error);
+
+  HttpBase base;
+  MemoryStream* mem;
+  HttpResponseData data;
+
+  // The source of http data, and source events
+  testing::StreamSource src;
+  std::vector<Event> events;
+
+  // Document stream, and stream events
+  bool obtain_stream;
+  StreamInterface* http_stream;
+  testing::StreamSink sink;
+};
+
+void HttpBaseTest::SetupSource(const char* http_data) {
+  LOG_F(LS_VERBOSE) << "Enter";
+
+  src.SetState(SS_OPENING);
+  src.QueueString(http_data);
+
+  base.notify(this);
+  base.attach(&src);
+  EXPECT_TRUE(events.empty());
+
+  src.SetState(SS_OPEN);
+  ASSERT_EQ(1U, events.size());
+  EXPECT_EQ(E_COMPLETE, events[0].event);
+  EXPECT_EQ(HM_CONNECT, events[0].mode);
+  EXPECT_EQ(HE_NONE, events[0].err);
+  events.clear();
+
+  mem = new MemoryStream;
+  data.document.reset(mem);
+  LOG_F(LS_VERBOSE) << "Exit";
+}
+
+void HttpBaseTest::VerifyHeaderComplete(size_t event_count, bool empty_doc) {
+  LOG_F(LS_VERBOSE) << "Enter";
+
+  ASSERT_EQ(event_count, events.size());
+  EXPECT_EQ(E_HEADER_COMPLETE, events[0].event);
+
+  std::string header;
+  EXPECT_EQ(HVER_1_1, data.version);
+  EXPECT_EQ(static_cast<uint32>(HC_OK), data.scode);
+  EXPECT_TRUE(data.hasHeader(HH_PROXY_AUTHORIZATION, &header));
+  EXPECT_EQ("42", header);
+  EXPECT_TRUE(data.hasHeader(HH_CONNECTION, &header));
+  EXPECT_EQ("Keep-Alive", header);
+
+  if (empty_doc) {
+    EXPECT_FALSE(events[0].chunked);
+    EXPECT_EQ(0U, events[0].data_size);
+
+    EXPECT_TRUE(data.hasHeader(HH_CONTENT_LENGTH, &header));
+    EXPECT_EQ("0", header);
+  } else {
+    EXPECT_TRUE(events[0].chunked);
+    EXPECT_EQ(SIZE_UNKNOWN, events[0].data_size);
+
+    EXPECT_TRUE(data.hasHeader(HH_CONTENT_TYPE, &header));
+    EXPECT_EQ("text/plain", header);
+    EXPECT_TRUE(data.hasHeader(HH_TRANSFER_ENCODING, &header));
+    EXPECT_EQ("chunked", header);
+  }
+  LOG_F(LS_VERBOSE) << "Exit";
+}
+
+void HttpBaseTest::VerifyDocumentContents(const char* expected_data,
+                                          size_t expected_length) {
+  LOG_F(LS_VERBOSE) << "Enter";
+
+  if (SIZE_UNKNOWN == expected_length) {
+    expected_length = strlen(expected_data);
+  }
+  EXPECT_EQ(mem, data.document.get());
+
+  size_t length;
+  mem->GetSize(&length);
+  EXPECT_EQ(expected_length, length);
+  EXPECT_TRUE(0 == memcmp(expected_data, mem->GetBuffer(), length));
+  LOG_F(LS_VERBOSE) << "Exit";
+}
+
+void HttpBaseTest::ObtainDocumentStream() {
+  LOG_F(LS_VERBOSE) << "Enter";
+  EXPECT_FALSE(http_stream);
+  http_stream = base.GetDocumentStream();
+  ASSERT_TRUE(NULL != http_stream);
+  sink.Monitor(http_stream);
+  LOG_F(LS_VERBOSE) << "Exit";
+}
+
+void HttpBaseTest::VerifyDocumentStreamIsOpening() {
+  LOG_F(LS_VERBOSE) << "Enter";
+  ASSERT_TRUE(NULL != http_stream);
+  EXPECT_EQ(0, sink.Events(http_stream));
+  EXPECT_EQ(SS_OPENING, http_stream->GetState());
+
+  size_t read = 0;
+  char buffer[5] = { 0 };
+  EXPECT_EQ(SR_BLOCK, http_stream->Read(buffer, sizeof(buffer), &read, NULL));
+  LOG_F(LS_VERBOSE) << "Exit";
+}
+
+void HttpBaseTest::VerifyDocumentStreamOpenEvent() {
+  LOG_F(LS_VERBOSE) << "Enter";
+
+  ASSERT_TRUE(NULL != http_stream);
+  EXPECT_EQ(SE_OPEN | SE_READ, sink.Events(http_stream));
+  EXPECT_EQ(SS_OPEN, http_stream->GetState());
+
+  // HTTP headers haven't arrived yet
+  EXPECT_EQ(0U, events.size());
+  EXPECT_EQ(static_cast<uint32>(HC_INTERNAL_SERVER_ERROR), data.scode);
+  LOG_F(LS_VERBOSE) << "Exit";
+}
+
+void HttpBaseTest::ReadDocumentStreamData(const char* expected_data) {
+  LOG_F(LS_VERBOSE) << "Enter";
+
+  ASSERT_TRUE(NULL != http_stream);
+  EXPECT_EQ(SS_OPEN, http_stream->GetState());
+
+  // Pump the HTTP I/O using Read, and verify the results.
+  size_t verified_length = 0;
+  const size_t expected_length = strlen(expected_data);
+  while (verified_length < expected_length) {
+    size_t read = 0;
+    char buffer[5] = { 0 };
+    size_t amt_to_read = _min(expected_length - verified_length, sizeof(buffer));
+    EXPECT_EQ(SR_SUCCESS, http_stream->Read(buffer, amt_to_read, &read, NULL));
+    EXPECT_EQ(amt_to_read, read);
+    EXPECT_TRUE(0 == memcmp(expected_data + verified_length, buffer, read));
+    verified_length += read;
+  }
+  LOG_F(LS_VERBOSE) << "Exit";
+}
+
+void HttpBaseTest::VerifyDocumentStreamIsEOS() {
+  LOG_F(LS_VERBOSE) << "Enter";
+
+  ASSERT_TRUE(NULL != http_stream);
+  size_t read = 0;
+  char buffer[5] = { 0 };
+  EXPECT_EQ(SR_EOS, http_stream->Read(buffer, sizeof(buffer), &read, NULL));
+  EXPECT_EQ(SS_CLOSED, http_stream->GetState());
+
+  // When EOS is caused by Read, we don't expect SE_CLOSE
+  EXPECT_EQ(0, sink.Events(http_stream));
+  LOG_F(LS_VERBOSE) << "Exit";
+}
+
+void HttpBaseTest::SetupDocument(const char* document_data) {
+  LOG_F(LS_VERBOSE) << "Enter";
+  src.SetState(SS_OPEN);
+
+  base.notify(this);
+  base.attach(&src);
+  EXPECT_TRUE(events.empty());
+
+  if (document_data) {
+    // Note: we could just call data.set_success("text/plain", mem), but that
+    // won't allow us to use the chunked transfer encoding.
+    mem = new MemoryStream(document_data);
+    data.document.reset(mem);
+    data.setHeader(HH_CONTENT_TYPE, "text/plain");
+    data.setHeader(HH_TRANSFER_ENCODING, "chunked");
+  } else {
+    data.setHeader(HH_CONTENT_LENGTH, "0");
+  }
+  data.scode = HC_OK;
+  data.setHeader(HH_PROXY_AUTHORIZATION, "42");
+  data.setHeader(HH_CONNECTION, "Keep-Alive");
+  LOG_F(LS_VERBOSE) << "Exit";
+}
+
+void HttpBaseTest::VerifySourceContents(const char* expected_data,
+                                        size_t expected_length) {
+  LOG_F(LS_VERBOSE) << "Enter";
+  if (SIZE_UNKNOWN == expected_length) {
+    expected_length = strlen(expected_data);
+  }
+  std::string contents = src.ReadData();
+  EXPECT_EQ(expected_length, contents.length());
+  EXPECT_TRUE(0 == memcmp(expected_data, contents.data(), expected_length));
+  LOG_F(LS_VERBOSE) << "Exit";
+}
+
+void HttpBaseTest::VerifyTransferComplete(HttpMode mode, HttpError error) {
+  LOG_F(LS_VERBOSE) << "Enter";
+  // Verify that http operation has completed
+  ASSERT_TRUE(events.size() > 0);
+  size_t last_event = events.size() - 1;
+  EXPECT_EQ(E_COMPLETE, events[last_event].event);
+  EXPECT_EQ(mode, events[last_event].mode);
+  EXPECT_EQ(error, events[last_event].err);
+  LOG_F(LS_VERBOSE) << "Exit";
+}
+
+//
+// Tests
+//
+
+TEST_F(HttpBaseTest, SupportsSend) {
+  // Queue response document
+  SetupDocument("Goodbye!");
+
+  // Begin send
+  base.send(&data);
+
+  // Send completed successfully
+  VerifyTransferComplete(HM_SEND, HE_NONE);
+  VerifySourceContents(kHttpResponse);
+}
+
+TEST_F(HttpBaseTest, SupportsSendNoDocument) {
+  // Queue response document
+  SetupDocument(NULL);
+
+  // Begin send
+  base.send(&data);
+
+  // Send completed successfully
+  VerifyTransferComplete(HM_SEND, HE_NONE);
+  VerifySourceContents(kHttpEmptyResponse);
+}
+
+TEST_F(HttpBaseTest, SignalsCompleteOnInterruptedSend) {
+  // This test is attempting to expose a bug that occurs when a particular
+  // base objects is used for receiving, and then used for sending.  In
+  // particular, the HttpParser state is different after receiving.  Simulate
+  // that here.
+  SetupSource(kHttpResponse);
+  base.recv(&data);
+  VerifyTransferComplete(HM_RECV, HE_NONE);
+
+  src.Clear();
+  data.clear(true);
+  events.clear();
+  base.detach();
+
+  // Queue response document
+  SetupDocument("Goodbye!");
+
+  // Prevent entire response from being sent
+  const size_t kInterruptedLength = strlen(kHttpResponse) - 1;
+  src.SetWriteBlock(kInterruptedLength);
+
+  // Begin send
+  base.send(&data);
+
+  // Document is mostly complete, but no completion signal yet.
+  EXPECT_TRUE(events.empty());
+  VerifySourceContents(kHttpResponse, kInterruptedLength);
+
+  src.SetState(SS_CLOSED);
+
+  // Send completed with disconnect error, and no additional data.
+  VerifyTransferComplete(HM_SEND, HE_DISCONNECTED);
+  EXPECT_TRUE(src.ReadData().empty());
+}
+
+TEST_F(HttpBaseTest, SupportsReceiveViaDocumentPush) {
+  // Queue response document
+  SetupSource(kHttpResponse);
+
+  // Begin receive
+  base.recv(&data);
+
+  // Document completed successfully
+  VerifyHeaderComplete(2, false);
+  VerifyTransferComplete(HM_RECV, HE_NONE);
+  VerifyDocumentContents("Goodbye!");
+}
+
+TEST_F(HttpBaseTest, SupportsReceiveViaStreamPull) {
+  // Switch to pull mode
+  ObtainDocumentStream();
+  VerifyDocumentStreamIsOpening();
+
+  // Queue response document
+  SetupSource(kHttpResponse);
+  VerifyDocumentStreamIsOpening();
+
+  // Begin receive
+  base.recv(&data);
+
+  // Pull document data
+  VerifyDocumentStreamOpenEvent();
+  ReadDocumentStreamData("Goodbye!");
+  VerifyDocumentStreamIsEOS();
+
+  // Document completed successfully
+  VerifyHeaderComplete(2, false);
+  VerifyTransferComplete(HM_RECV, HE_NONE);
+  VerifyDocumentContents("");
+}
+
+TEST_F(HttpBaseTest, DISABLED_AllowsCloseStreamBeforeDocumentIsComplete) {
+
+  // TODO: Remove extra logging once test failure is understood
+  int old_sev = talk_base::LogMessage::GetLogToDebug();
+  talk_base::LogMessage::LogToDebug(LS_VERBOSE);
+
+
+  // Switch to pull mode
+  ObtainDocumentStream();
+  VerifyDocumentStreamIsOpening();
+
+  // Queue response document
+  SetupSource(kHttpResponse);
+  VerifyDocumentStreamIsOpening();
+
+  // Begin receive
+  base.recv(&data);
+
+  // Pull some of the data
+  VerifyDocumentStreamOpenEvent();
+  ReadDocumentStreamData("Goodb");
+
+  // We've seen the header by now
+  VerifyHeaderComplete(1, false);
+
+  // Close the pull stream, this will transition back to push I/O.
+  http_stream->Close();
+  Thread::Current()->ProcessMessages(0);
+
+  // Remainder of document completed successfully
+  VerifyTransferComplete(HM_RECV, HE_NONE);
+  VerifyDocumentContents("ye!");
+
+  talk_base::LogMessage::LogToDebug(old_sev);
+}
+
+TEST_F(HttpBaseTest, AllowsGetDocumentStreamInResponseToHttpHeader) {
+  // Queue response document
+  SetupSource(kHttpResponse);
+
+  // Switch to pull mode in response to header arrival
+  obtain_stream = true;
+
+  // Begin receive
+  base.recv(&data);
+
+  // We've already seen the header, but not data has arrived
+  VerifyHeaderComplete(1, false);
+  VerifyDocumentContents("");
+
+  // Pull the document data
+  ReadDocumentStreamData("Goodbye!");
+  VerifyDocumentStreamIsEOS();
+
+  // Document completed successfully
+  VerifyTransferComplete(HM_RECV, HE_NONE);
+  VerifyDocumentContents("");
+}
+
+TEST_F(HttpBaseTest, AllowsGetDocumentStreamWithEmptyDocumentBody) {
+  // Queue empty response document
+  SetupSource(kHttpEmptyResponse);
+
+  // Switch to pull mode in response to header arrival
+  obtain_stream = true;
+
+  // Begin receive
+  base.recv(&data);
+
+  // We've already seen the header, but not data has arrived
+  VerifyHeaderComplete(1, true);
+  VerifyDocumentContents("");
+
+  // The document is still open, until we attempt to read
+  ASSERT_TRUE(NULL != http_stream);
+  EXPECT_EQ(SS_OPEN, http_stream->GetState());
+
+  // Attempt to read data, and discover EOS
+  VerifyDocumentStreamIsEOS();
+
+  // Document completed successfully
+  VerifyTransferComplete(HM_RECV, HE_NONE);
+  VerifyDocumentContents("");
+}
+
+TEST_F(HttpBaseTest, SignalsDocumentStreamCloseOnUnexpectedClose) {
+  // Switch to pull mode
+  ObtainDocumentStream();
+  VerifyDocumentStreamIsOpening();
+
+  // Queue response document
+  SetupSource(kHttpResponsePrefix);
+  VerifyDocumentStreamIsOpening();
+
+  // Begin receive
+  base.recv(&data);
+
+  // Pull document data
+  VerifyDocumentStreamOpenEvent();
+  ReadDocumentStreamData("Goodbye!");
+
+  // Simulate unexpected close
+  src.SetState(SS_CLOSED);
+
+  // Observe error event on document stream
+  EXPECT_EQ(testing::SSE_ERROR, sink.Events(http_stream));
+
+  // Future reads give an error
+  int error = 0;
+  char buffer[5] = { 0 };
+  EXPECT_EQ(SR_ERROR, http_stream->Read(buffer, sizeof(buffer), NULL, &error));
+  EXPECT_EQ(HE_DISCONNECTED, error);
+
+  // Document completed with error
+  VerifyHeaderComplete(2, false);
+  VerifyTransferComplete(HM_RECV, HE_DISCONNECTED);
+  VerifyDocumentContents("");
+}
+
+} // namespace talk_base
diff --git a/talk/base/httpcommon_unittest.cc b/talk/base/httpcommon_unittest.cc
new file mode 100644
index 0000000..37d77ed
--- /dev/null
+++ b/talk/base/httpcommon_unittest.cc
@@ -0,0 +1,162 @@
+/*
+ * libjingle
+ * Copyright 2004--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/gunit.h"
+#include "talk/base/httpcommon-inl.h"
+#include "talk/base/httpcommon.h"
+
+namespace talk_base {
+
+#define TEST_PROTOCOL "http://"
+#define TEST_HOST "www.google.com"
+#define TEST_PATH "/folder/file.html"
+#define TEST_QUERY "?query=x&attr=y"
+#define TEST_URL TEST_PROTOCOL TEST_HOST TEST_PATH TEST_QUERY
+
+TEST(Url, DecomposesUrls) {
+  Url<char> url(TEST_URL);
+  EXPECT_TRUE(url.valid());
+  EXPECT_FALSE(url.secure());
+  EXPECT_STREQ(TEST_HOST, url.host().c_str());
+  EXPECT_EQ(80, url.port());
+  EXPECT_STREQ(TEST_PATH, url.path().c_str());
+  EXPECT_STREQ(TEST_QUERY, url.query().c_str());
+  EXPECT_STREQ(TEST_HOST, url.address().c_str());
+  EXPECT_STREQ(TEST_PATH TEST_QUERY, url.full_path().c_str());
+  EXPECT_STREQ(TEST_URL, url.url().c_str());
+}
+
+TEST(Url, ComposesUrls) {
+  // Set in constructor
+  Url<char> url(TEST_PATH TEST_QUERY, TEST_HOST, 80);
+  EXPECT_TRUE(url.valid());
+  EXPECT_FALSE(url.secure());
+  EXPECT_STREQ(TEST_HOST, url.host().c_str());
+  EXPECT_EQ(80, url.port());
+  EXPECT_STREQ(TEST_PATH, url.path().c_str());
+  EXPECT_STREQ(TEST_QUERY, url.query().c_str());
+  EXPECT_STREQ(TEST_HOST, url.address().c_str());
+  EXPECT_STREQ(TEST_PATH TEST_QUERY, url.full_path().c_str());
+  EXPECT_STREQ(TEST_URL, url.url().c_str());
+
+  url.clear();
+  EXPECT_FALSE(url.valid());
+  EXPECT_FALSE(url.secure());
+  EXPECT_STREQ("", url.host().c_str());
+  EXPECT_EQ(80, url.port());
+  EXPECT_STREQ("/", url.path().c_str());
+  EXPECT_STREQ("", url.query().c_str());
+
+  // Set component-wise
+  url.set_host(TEST_HOST);
+  url.set_port(80);
+  url.set_path(TEST_PATH);
+  url.set_query(TEST_QUERY);
+  EXPECT_TRUE(url.valid());
+  EXPECT_FALSE(url.secure());
+  EXPECT_STREQ(TEST_HOST, url.host().c_str());
+  EXPECT_EQ(80, url.port());
+  EXPECT_STREQ(TEST_PATH, url.path().c_str());
+  EXPECT_STREQ(TEST_QUERY, url.query().c_str());
+  EXPECT_STREQ(TEST_HOST, url.address().c_str());
+  EXPECT_STREQ(TEST_PATH TEST_QUERY, url.full_path().c_str());
+  EXPECT_STREQ(TEST_URL, url.url().c_str());
+}
+
+TEST(Url, EnsuresNonEmptyPath) {
+  Url<char> url(TEST_PROTOCOL TEST_HOST);
+  EXPECT_TRUE(url.valid());
+  EXPECT_STREQ("/", url.path().c_str());
+  
+  url.clear();
+  EXPECT_STREQ("/", url.path().c_str());
+  url.set_path("");
+  EXPECT_STREQ("/", url.path().c_str());
+
+  url.clear();
+  EXPECT_STREQ("/", url.path().c_str());
+  url.set_full_path("");
+  EXPECT_STREQ("/", url.path().c_str());
+}
+
+TEST(Url, GetQueryAttributes) {
+  Url<char> url(TEST_URL);
+  std::string value;
+  EXPECT_TRUE(url.get_attribute("query", &value));
+  EXPECT_STREQ("x", value.c_str());
+  value.clear();
+  EXPECT_TRUE(url.get_attribute("attr", &value));
+  EXPECT_STREQ("y", value.c_str());
+  value.clear();
+  EXPECT_FALSE(url.get_attribute("Query", &value));
+  EXPECT_TRUE(value.empty());
+}
+
+TEST(HttpResponseData, parseLeaderHttp1_0) {
+  static const char kResponseString[] = "HTTP/1.0 200 OK";
+  HttpResponseData response;
+  EXPECT_EQ(HE_NONE, response.parseLeader(kResponseString,
+                                          sizeof(kResponseString) - 1));
+  EXPECT_EQ(HVER_1_0, response.version);
+  EXPECT_EQ(200U, response.scode);
+}
+
+TEST(HttpResponseData, parseLeaderHttp1_1) {
+  static const char kResponseString[] = "HTTP/1.1 200 OK";
+  HttpResponseData response;
+  EXPECT_EQ(HE_NONE, response.parseLeader(kResponseString,
+                                          sizeof(kResponseString) - 1));
+  EXPECT_EQ(HVER_1_1, response.version);
+  EXPECT_EQ(200U, response.scode);
+}
+
+TEST(HttpResponseData, parseLeaderHttpUnknown) {
+  static const char kResponseString[] = "HTTP 200 OK";
+  HttpResponseData response;
+  EXPECT_EQ(HE_NONE, response.parseLeader(kResponseString,
+                                          sizeof(kResponseString) - 1));
+  EXPECT_EQ(HVER_UNKNOWN, response.version);
+  EXPECT_EQ(200U, response.scode);
+}
+
+TEST(HttpResponseData, parseLeaderHttpFailure) {
+  static const char kResponseString[] = "HTTP/1.1 503 Service Unavailable";
+  HttpResponseData response;
+  EXPECT_EQ(HE_NONE, response.parseLeader(kResponseString,
+                                          sizeof(kResponseString) - 1));
+  EXPECT_EQ(HVER_1_1, response.version);
+  EXPECT_EQ(503U, response.scode);
+}
+
+TEST(HttpResponseData, parseLeaderHttpInvalid) {
+  static const char kResponseString[] = "Durrrrr, what's HTTP?";
+  HttpResponseData response;
+  EXPECT_EQ(HE_PROTOCOL, response.parseLeader(kResponseString,
+                                              sizeof(kResponseString) - 1));
+}
+
+} // namespace talk_base
diff --git a/talk/base/httpserver.cc b/talk/base/httpserver.cc
new file mode 100644
index 0000000..dd4898f
--- /dev/null
+++ b/talk/base/httpserver.cc
@@ -0,0 +1,293 @@
+/*
+ * 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 <algorithm>
+
+#include "talk/base/httpcommon-inl.h"
+
+#include "talk/base/asyncsocket.h"
+#include "talk/base/common.h"
+#include "talk/base/httpserver.h"
+#include "talk/base/logging.h"
+#include "talk/base/socketstream.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// HttpServer
+///////////////////////////////////////////////////////////////////////////////
+
+HttpServer::HttpServer() : next_connection_id_(1), closing_(false) {
+}
+
+HttpServer::~HttpServer() {
+  if (closing_) {
+    LOG(LS_WARNING) << "HttpServer::CloseAll has not completed";
+  }
+  for (ConnectionMap::iterator it = connections_.begin();
+       it != connections_.end();
+       ++it) {
+    StreamInterface* stream = it->second->EndProcess();
+    delete stream;
+    delete it->second;
+  }
+}
+
+int
+HttpServer::HandleConnection(StreamInterface* stream) {
+  int connection_id = next_connection_id_++;
+  ASSERT(connection_id != HTTP_INVALID_CONNECTION_ID);
+  Connection* connection = new Connection(connection_id, this);
+  connections_.insert(ConnectionMap::value_type(connection_id, connection));
+  connection->BeginProcess(stream);
+  return connection_id;
+}
+
+void
+HttpServer::Respond(HttpServerTransaction* transaction) {
+  int connection_id = transaction->connection_id();
+  if (Connection* connection = Find(connection_id)) {
+    connection->Respond(transaction);
+  } else {
+    delete transaction;
+    // We may be tempted to SignalHttpComplete, but that implies that a
+    // connection still exists.
+  }
+}
+
+void
+HttpServer::Close(int connection_id, bool force) {
+  if (Connection* connection = Find(connection_id)) {
+    connection->InitiateClose(force);
+  }
+}
+
+void
+HttpServer::CloseAll(bool force) {
+  if (connections_.empty()) {
+    SignalCloseAllComplete(this);
+    return;
+  }
+  closing_ = true;
+  std::list<Connection*> connections;
+  for (ConnectionMap::const_iterator it = connections_.begin();
+       it != connections_.end(); ++it) {
+    connections.push_back(it->second);
+  }
+  for (std::list<Connection*>::const_iterator it = connections.begin();
+      it != connections.end(); ++it) {
+    (*it)->InitiateClose(force);
+  }
+}
+
+HttpServer::Connection*
+HttpServer::Find(int connection_id) {
+  ConnectionMap::iterator it = connections_.find(connection_id);
+  if (it == connections_.end())
+    return NULL;
+  return it->second;
+}
+
+void
+HttpServer::Remove(int connection_id) {
+  ConnectionMap::iterator it = connections_.find(connection_id);
+  if (it == connections_.end()) {
+    ASSERT(false);
+    return;
+  }
+  Connection* connection = it->second;
+  connections_.erase(it);
+  SignalConnectionClosed(this, connection_id, connection->EndProcess());
+  delete connection;
+  if (closing_ && connections_.empty()) {
+    closing_ = false;
+    SignalCloseAllComplete(this);
+  }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// HttpServer::Connection
+///////////////////////////////////////////////////////////////////////////////
+
+HttpServer::Connection::Connection(int connection_id, HttpServer* server) 
+  : connection_id_(connection_id), server_(server),
+    current_(NULL), signalling_(false), close_(false) { 
+}
+
+HttpServer::Connection::~Connection() {
+  // It's possible that an object hosted inside this transaction signalled
+  // an event which caused the connection to close.
+  Thread::Current()->Dispose(current_);
+}
+
+void
+HttpServer::Connection::BeginProcess(StreamInterface* stream) {
+  base_.notify(this); 
+  base_.attach(stream);
+  current_ = new HttpServerTransaction(connection_id_);
+  if (base_.mode() != HM_CONNECT)
+    base_.recv(&current_->request);
+}
+
+StreamInterface*
+HttpServer::Connection::EndProcess() {
+  base_.notify(NULL);
+  base_.abort(HE_DISCONNECTED);
+  return base_.detach();
+}
+
+void
+HttpServer::Connection::Respond(HttpServerTransaction* transaction) {
+  ASSERT(current_ == NULL);
+  current_ = transaction;
+  if (current_->response.begin() == current_->response.end()) {
+    current_->response.set_error(HC_INTERNAL_SERVER_ERROR);
+  }
+  bool keep_alive = HttpShouldKeepAlive(current_->request);
+  current_->response.setHeader(HH_CONNECTION,
+                               keep_alive ? "Keep-Alive" : "Close",
+                               false);
+  close_ = !HttpShouldKeepAlive(current_->response);
+  base_.send(&current_->response);
+}
+
+void
+HttpServer::Connection::InitiateClose(bool force) {
+  bool request_in_progress = (HM_SEND == base_.mode()) || (NULL == current_);
+  if (!signalling_ && (force || !request_in_progress)) {
+    server_->Remove(connection_id_);
+  } else {
+    close_ = true;
+  }
+}
+
+//
+// IHttpNotify Implementation
+//
+  
+HttpError
+HttpServer::Connection::onHttpHeaderComplete(bool chunked, size_t& data_size) {
+  if (data_size == SIZE_UNKNOWN) {
+    data_size = 0;
+  }
+  ASSERT(current_ != NULL);
+  bool custom_document = false;
+  server_->SignalHttpRequestHeader(server_, current_, &custom_document);
+  if (!custom_document) {
+    current_->request.document.reset(new MemoryStream);
+  }
+  return HE_NONE;
+}
+
+void
+HttpServer::Connection::onHttpComplete(HttpMode mode, HttpError err) {
+  if (mode == HM_SEND) {
+    ASSERT(current_ != NULL);
+    signalling_ = true;
+    server_->SignalHttpRequestComplete(server_, current_, err);
+    signalling_ = false;
+    if (close_) {
+      // Force a close
+      err = HE_DISCONNECTED;
+    }
+  }
+  if (err != HE_NONE) {
+    server_->Remove(connection_id_);
+  } else if (mode == HM_CONNECT) {
+    base_.recv(&current_->request);
+  } else if (mode == HM_RECV) {
+    ASSERT(current_ != NULL);
+    // TODO: do we need this?
+    //request_.document_->rewind();
+    HttpServerTransaction* transaction = current_;
+    current_ = NULL;
+    server_->SignalHttpRequest(server_, transaction);
+  } else if (mode == HM_SEND) {
+    Thread::Current()->Dispose(current_->response.document.release());
+    current_->request.clear(true);
+    current_->response.clear(true);
+    base_.recv(&current_->request);
+  } else {
+    ASSERT(false);
+  }
+}
+
+void
+HttpServer::Connection::onHttpClosed(HttpError err) {
+  UNUSED(err);
+  server_->Remove(connection_id_);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// HttpListenServer
+///////////////////////////////////////////////////////////////////////////////
+
+HttpListenServer::HttpListenServer()
+: listener_(Thread::Current()->socketserver()->CreateAsyncSocket(SOCK_STREAM)) {
+  listener_->SignalReadEvent.connect(this, &HttpListenServer::OnReadEvent);
+  SignalConnectionClosed.connect(this, &HttpListenServer::OnConnectionClosed);
+}
+
+HttpListenServer::~HttpListenServer() {
+}
+
+int HttpListenServer::Listen(const SocketAddress& address) {
+  if ((listener_->Bind(address) != SOCKET_ERROR) &&
+      (listener_->Listen(5) != SOCKET_ERROR))
+    return 0;
+  return listener_->GetError();
+}
+
+bool HttpListenServer::GetAddress(SocketAddress* address) const {
+  *address = listener_->GetLocalAddress();
+  return !address->IsNil();
+}
+
+void HttpListenServer::StopListening() {
+  listener_->Close();
+}
+
+void HttpListenServer::OnReadEvent(AsyncSocket* socket) {
+  ASSERT(socket == listener_.get());
+  AsyncSocket* incoming = listener_->Accept(NULL);
+  if (incoming) {
+    StreamInterface* stream = new SocketStream(incoming);
+    //stream = new LoggingAdapter(stream, LS_VERBOSE, "HttpServer", false);
+    HandleConnection(stream);
+  }
+}
+
+void HttpListenServer::OnConnectionClosed(HttpServer* server,
+                                          int connection_id,
+                                          StreamInterface* stream) {
+  Thread::Current()->Dispose(stream);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
diff --git a/talk/base/httpserver.h b/talk/base/httpserver.h
new file mode 100644
index 0000000..67061ee
--- /dev/null
+++ b/talk/base/httpserver.h
@@ -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.
+ */
+
+#ifndef TALK_BASE_HTTPSERVER_H__
+#define TALK_BASE_HTTPSERVER_H__
+
+#include <map>
+#include "talk/base/httpbase.h"
+
+namespace talk_base {
+
+class AsyncSocket;
+class HttpServer;
+class SocketAddress;
+
+//////////////////////////////////////////////////////////////////////
+// HttpServer
+//////////////////////////////////////////////////////////////////////
+
+const int HTTP_INVALID_CONNECTION_ID = 0;
+
+struct HttpServerTransaction : public HttpTransaction {
+public:
+  HttpServerTransaction(int id) : connection_id_(id) { }
+  int connection_id() const { return connection_id_; }
+
+private:
+  int connection_id_;
+};
+
+class HttpServer {
+public:
+  HttpServer();
+  virtual ~HttpServer();
+
+  int HandleConnection(StreamInterface* stream);
+  // Due to sigslot issues, we can't destroy some streams at an arbitrary time.
+  sigslot::signal3<HttpServer*, int, StreamInterface*> SignalConnectionClosed;
+
+  // This signal occurs when the HTTP request headers have been received, but
+  // before the request body is written to the request document.  By default,
+  // the request document is a MemoryStream.  By handling this signal, the
+  // document can be overridden, in which case the third signal argument should
+  // be set to true.  In the case where the request body should be ignored,
+  // the document can be set to NULL.  Note that the transaction object is still
+  // owened by the HttpServer at this point.  
+  sigslot::signal3<HttpServer*, HttpServerTransaction*, bool*>
+    SignalHttpRequestHeader;
+
+  // An HTTP request has been made, and is available in the transaction object.
+  // Populate the transaction's response, and then return the object via the
+  // Respond method.  Note that during this time, ownership of the transaction
+  // object is transferred, so it may be passed between threads, although
+  // respond must be called on the server's active thread.
+  sigslot::signal2<HttpServer*, HttpServerTransaction*> SignalHttpRequest;
+  void Respond(HttpServerTransaction* transaction);
+
+  // If you want to know when a request completes, listen to this event.
+  sigslot::signal3<HttpServer*, HttpServerTransaction*, int>
+    SignalHttpRequestComplete;
+
+  // Stop processing the connection indicated by connection_id.
+  // Unless force is true, the server will complete sending a response that is
+  // in progress.
+  void Close(int connection_id, bool force);
+  void CloseAll(bool force);
+
+  // After calling CloseAll, this event is signalled to indicate that all
+  // outstanding connections have closed.
+  sigslot::signal1<HttpServer*> SignalCloseAllComplete;
+
+private:
+  class Connection : private IHttpNotify {
+  public:
+    Connection(int connection_id, HttpServer* server);
+    virtual ~Connection();
+
+    void BeginProcess(StreamInterface* stream);
+    StreamInterface* EndProcess();
+    
+    void Respond(HttpServerTransaction* transaction);
+    void InitiateClose(bool force);
+
+    // IHttpNotify Interface
+    virtual HttpError onHttpHeaderComplete(bool chunked, size_t& data_size);
+    virtual void onHttpComplete(HttpMode mode, HttpError err);
+    virtual void onHttpClosed(HttpError err);
+  
+    int connection_id_;
+    HttpServer* server_;
+    HttpBase base_;
+    HttpServerTransaction* current_;
+    bool signalling_, close_;
+  };
+
+  Connection* Find(int connection_id);
+  void Remove(int connection_id);
+
+  friend class Connection;
+  typedef std::map<int,Connection*> ConnectionMap;
+
+  ConnectionMap connections_;
+  int next_connection_id_;
+  bool closing_;
+};
+
+//////////////////////////////////////////////////////////////////////
+
+class HttpListenServer : public HttpServer, public sigslot::has_slots<> {
+public:
+  HttpListenServer();
+  virtual ~HttpListenServer();
+
+  int Listen(const SocketAddress& address);
+  bool GetAddress(SocketAddress* address) const;
+  void StopListening();
+
+private:
+  void OnReadEvent(AsyncSocket* socket);
+  void OnConnectionClosed(HttpServer* server, int connection_id,
+                          StreamInterface* stream);
+
+  scoped_ptr<AsyncSocket> listener_;
+};
+
+//////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif // TALK_BASE_HTTPSERVER_H__
diff --git a/talk/base/httpserver_unittest.cc b/talk/base/httpserver_unittest.cc
new file mode 100644
index 0000000..d0e0760
--- /dev/null
+++ b/talk/base/httpserver_unittest.cc
@@ -0,0 +1,130 @@
+// Copyright 2007 Google Inc.
+// All Rights Reserved.
+
+
+#include "talk/base/gunit.h"
+#include "talk/base/httpserver.h"
+#include "talk/base/testutils.h"
+
+using namespace testing;
+
+namespace talk_base {
+
+namespace {
+  const char* const kRequest =
+    "GET /index.html HTTP/1.1\r\n"
+    "Host: localhost\r\n"
+    "\r\n";
+
+  const char* const kResponse =
+    "HTTP/1.1 200\r\n"
+    "Connection: Close\r\n"
+    "Content-Length: 0\r\n"
+    "\r\n";
+
+  struct HttpServerMonitor : public sigslot::has_slots<> {
+    HttpServerTransaction* transaction;
+    bool server_closed, connection_closed;
+
+    HttpServerMonitor(HttpServer* server)
+    : transaction(NULL), server_closed(false), connection_closed(false) {
+      server->SignalCloseAllComplete.connect(this,
+        &HttpServerMonitor::OnClosed);
+      server->SignalHttpRequest.connect(this, &HttpServerMonitor::OnRequest);
+      server->SignalHttpRequestComplete.connect(this,
+        &HttpServerMonitor::OnRequestComplete);
+      server->SignalConnectionClosed.connect(this,
+        &HttpServerMonitor::OnConnectionClosed);
+    }
+    void OnRequest(HttpServer*, HttpServerTransaction* t) {
+      ASSERT_FALSE(transaction);
+      transaction = t;
+      transaction->response.set_success();
+      transaction->response.setHeader(HH_CONNECTION, "Close");
+    }
+    void OnRequestComplete(HttpServer*, HttpServerTransaction* t, int) {
+      ASSERT_EQ(transaction, t);
+      transaction = NULL;
+    }
+    void OnClosed(HttpServer*) {
+      server_closed = true;
+    }
+    void OnConnectionClosed(HttpServer*, int, StreamInterface* stream) {
+      connection_closed = true;
+      delete stream;
+    }
+  };
+
+  void CreateClientConnection(HttpServer& server,
+                              HttpServerMonitor& monitor,
+                              bool send_request) {
+    StreamSource* client = new StreamSource;
+    client->SetState(SS_OPEN);
+    server.HandleConnection(client);
+    EXPECT_FALSE(monitor.server_closed);
+    EXPECT_FALSE(monitor.transaction);
+
+    if (send_request) {
+      // Simulate a request
+      client->QueueString(kRequest);
+      EXPECT_FALSE(monitor.server_closed);
+    }
+  }
+}  // anonymous namespace
+
+TEST(HttpServer, DoesNotSignalCloseUnlessCloseAllIsCalled) {
+  HttpServer server;
+  HttpServerMonitor monitor(&server);
+  // Add an active client connection
+  CreateClientConnection(server, monitor, true);
+  // Simulate a response
+  ASSERT_TRUE(NULL != monitor.transaction);
+  server.Respond(monitor.transaction);
+  EXPECT_FALSE(monitor.transaction);
+  // Connection has closed, but no server close signal
+  EXPECT_FALSE(monitor.server_closed);
+  EXPECT_TRUE(monitor.connection_closed);
+}
+
+TEST(HttpServer, SignalsCloseWhenNoConnectionsAreActive) {
+  HttpServer server;
+  HttpServerMonitor monitor(&server);
+  // Add an idle client connection
+  CreateClientConnection(server, monitor, false);
+  // Perform graceful close
+  server.CloseAll(false);
+  // Connections have all closed
+  EXPECT_TRUE(monitor.server_closed);
+  EXPECT_TRUE(monitor.connection_closed);
+}
+
+TEST(HttpServer, SignalsCloseAfterGracefulCloseAll) {
+  HttpServer server;
+  HttpServerMonitor monitor(&server);
+  // Add an active client connection
+  CreateClientConnection(server, monitor, true);
+  // Initiate a graceful close
+  server.CloseAll(false);
+  EXPECT_FALSE(monitor.server_closed);
+  // Simulate a response
+  ASSERT_TRUE(NULL != monitor.transaction);
+  server.Respond(monitor.transaction);
+  EXPECT_FALSE(monitor.transaction);
+  // Connections have all closed
+  EXPECT_TRUE(monitor.server_closed);
+  EXPECT_TRUE(monitor.connection_closed);
+}
+
+TEST(HttpServer, SignalsCloseAfterForcedCloseAll) {
+  HttpServer server;
+  HttpServerMonitor monitor(&server);
+  // Add an active client connection
+  CreateClientConnection(server, monitor, true);
+  // Initiate a forceful close
+  server.CloseAll(true);
+  // Connections have all closed
+  EXPECT_TRUE(monitor.server_closed);
+  EXPECT_TRUE(monitor.connection_closed);
+}
+
+} // namespace talk_base
diff --git a/talk/base/logging_unittest.cc b/talk/base/logging_unittest.cc
new file mode 100644
index 0000000..18de822
--- /dev/null
+++ b/talk/base/logging_unittest.cc
@@ -0,0 +1,142 @@
+/*
+ * libjingle
+ * Copyright 2004--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/fileutils.h"
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/stream.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+// Test basic logging operation. We should get the INFO log but not the VERBOSE.
+// We should restore the correct global state at the end.
+TEST(LogTest, SingleStream) {
+  int sev = LogMessage::GetLogToStream(NULL);
+
+  std::string str;
+  StringStream stream(str);
+  LogMessage::AddLogToStream(&stream, LS_INFO);
+  EXPECT_EQ(LS_INFO, LogMessage::GetLogToStream(&stream));
+
+  LOG(LS_INFO) << "INFO";
+  LOG(LS_VERBOSE) << "VERBOSE";
+  EXPECT_NE(std::string::npos, str.find("INFO"));
+  EXPECT_EQ(std::string::npos, str.find("VERBOSE"));
+
+  LogMessage::RemoveLogToStream(&stream);
+  EXPECT_EQ(LogMessage::NO_LOGGING, LogMessage::GetLogToStream(&stream));
+
+  EXPECT_EQ(sev, LogMessage::GetLogToStream(NULL));
+}
+
+// Test using multiple log streams. The INFO stream should get the INFO message,
+// the VERBOSE stream should get the INFO and the VERBOSE.
+// We should restore the correct global state at the end.
+TEST(LogTest, MultipleStreams) {
+  int sev = LogMessage::GetLogToStream(NULL);
+
+  std::string str1, str2;
+  StringStream stream1(str1), stream2(str2);
+  LogMessage::AddLogToStream(&stream1, LS_INFO);
+  LogMessage::AddLogToStream(&stream2, LS_VERBOSE);
+  EXPECT_EQ(LS_INFO, LogMessage::GetLogToStream(&stream1));
+  EXPECT_EQ(LS_VERBOSE, LogMessage::GetLogToStream(&stream2));
+
+  LOG(LS_INFO) << "INFO";
+  LOG(LS_VERBOSE) << "VERBOSE";
+
+  EXPECT_NE(std::string::npos, str1.find("INFO"));
+  EXPECT_EQ(std::string::npos, str1.find("VERBOSE"));
+  EXPECT_NE(std::string::npos, str2.find("INFO"));
+  EXPECT_NE(std::string::npos, str2.find("VERBOSE"));
+
+  LogMessage::RemoveLogToStream(&stream2);
+  LogMessage::RemoveLogToStream(&stream1);
+  EXPECT_EQ(LogMessage::NO_LOGGING, LogMessage::GetLogToStream(&stream2));
+  EXPECT_EQ(LogMessage::NO_LOGGING, LogMessage::GetLogToStream(&stream1));
+
+  EXPECT_EQ(sev, LogMessage::GetLogToStream(NULL));
+}
+
+// Ensure we don't crash when adding/removing streams while threads are going.
+// We should restore the correct global state at the end.
+class LogThread : public Thread {
+  void Run() {
+    // LS_SENSITIVE to avoid cluttering up any real logging going on
+    LOG(LS_SENSITIVE) << "LOG";
+  }
+};
+
+TEST(LogTest, MultipleThreads) {
+  int sev = LogMessage::GetLogToStream(NULL);
+
+  LogThread thread1, thread2, thread3;
+  thread1.Start();
+  thread2.Start();
+  thread3.Start();
+
+  NullStream stream1, stream2, stream3;
+  for (int i = 0; i < 1000; ++i) {
+    LogMessage::AddLogToStream(&stream1, LS_INFO);
+    LogMessage::AddLogToStream(&stream2, LS_VERBOSE);
+    LogMessage::AddLogToStream(&stream3, LS_SENSITIVE);
+    LogMessage::RemoveLogToStream(&stream1);
+    LogMessage::RemoveLogToStream(&stream2);
+    LogMessage::RemoveLogToStream(&stream3);
+  }
+
+  EXPECT_EQ(sev, LogMessage::GetLogToStream(NULL));
+}
+
+// Test the time required to write 1000 80-character logs to an unbuffered file.
+TEST(LogTest, Perf) {
+  Pathname path;
+  EXPECT_TRUE(Filesystem::GetTemporaryFolder(path, true, NULL));
+  path.SetPathname(Filesystem::TempFilename(path, "ut"));
+
+  FileStream stream;
+  EXPECT_TRUE(stream.Open(path.pathname(), "wb", NULL));
+  stream.DisableBuffering();
+  LogMessage::AddLogToStream(&stream, LS_SENSITIVE);
+
+  uint32 start = Time(), finish;
+  std::string message('X', 80);
+  for (int i = 0; i < 1000; ++i) {
+    LOG(LS_SENSITIVE) << message;
+  }
+  finish = Time();
+
+  LogMessage::RemoveLogToStream(&stream);
+  stream.Close();
+  Filesystem::DeleteFile(path);
+
+  LOG(LS_INFO) << "Average log time: " << TimeDiff(finish, start) << " us";
+}
+
+}  // namespace talk_base
diff --git a/talk/base/messagequeue_unittest.cc b/talk/base/messagequeue_unittest.cc
new file mode 100644
index 0000000..7414618
--- /dev/null
+++ b/talk/base/messagequeue_unittest.cc
@@ -0,0 +1,53 @@
+/*
+ * libjingle
+ * Copyright 2004--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/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/time.h"
+#include "talk/base/messagequeue.h"
+
+using namespace talk_base;
+
+TEST(MessageQueue, DelayedPostsWithIdenticalTimesAreProcessedInFifoOrder) {
+  MessageQueue q;
+
+  TimeStamp now = Time();
+  q.PostAt(now, NULL, 3);
+  q.PostAt(now - 2, NULL, 0);
+  q.PostAt(now - 1, NULL, 1);
+  q.PostAt(now, NULL, 4);
+  q.PostAt(now - 1, NULL, 2);
+
+  Message msg;
+  for (size_t i=0; i<5; ++i) {
+    memset(&msg, 0, sizeof(msg));
+    EXPECT_TRUE(q.Get(&msg, 0));
+    EXPECT_EQ(i, msg.message_id);
+  }
+
+  EXPECT_FALSE(q.Get(&msg, 0));  // No more messages
+}
diff --git a/talk/base/network_unittest.cc b/talk/base/network_unittest.cc
index 6a9919d..a55ba2f 100644
--- a/talk/base/network_unittest.cc
+++ b/talk/base/network_unittest.cc
@@ -1,7 +1,29 @@
-// Copyright 2009 Google Inc.
-// All Rights Reserved.
-//
-
+/*
+ * libjingle
+ * Copyright 2004--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 <vector>
 #include "talk/base/gunit.h"
diff --git a/talk/base/referencecountedsingletonfactory_unittest.cc b/talk/base/referencecountedsingletonfactory_unittest.cc
new file mode 100644
index 0000000..3fc7fd2
--- /dev/null
+++ b/talk/base/referencecountedsingletonfactory_unittest.cc
@@ -0,0 +1,149 @@
+/*
+ * libjingle
+ * Copyright 2004--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/gunit.h"
+#include "talk/base/referencecountedsingletonfactory.h"
+
+namespace talk_base {
+
+class MyExistenceWatcher {
+ public:
+  MyExistenceWatcher() { create_called_ = true; }
+  ~MyExistenceWatcher() { delete_called_ = true; }
+
+  static bool create_called_;
+  static bool delete_called_;
+};
+
+bool MyExistenceWatcher::create_called_ = false;
+bool MyExistenceWatcher::delete_called_ = false;
+
+class TestReferenceCountedSingletonFactory :
+    public ReferenceCountedSingletonFactory<MyExistenceWatcher> {
+ protected:
+  virtual bool SetupInstance() {
+    instance_.reset(new MyExistenceWatcher());
+    return true;
+  }
+
+  virtual void CleanupInstance() {
+    instance_.reset();
+  }
+};
+
+static void DoCreateAndGoOutOfScope(
+    ReferenceCountedSingletonFactory<MyExistenceWatcher> *factory) {
+  rcsf_ptr<MyExistenceWatcher> ptr(factory);
+  ptr.get();
+  // and now ptr should go out of scope.
+}
+
+TEST(ReferenceCountedSingletonFactory, ZeroReferenceCountCausesDeletion) {
+  TestReferenceCountedSingletonFactory factory;
+  MyExistenceWatcher::delete_called_ = false;
+  DoCreateAndGoOutOfScope(&factory);
+  EXPECT_TRUE(MyExistenceWatcher::delete_called_);
+}
+
+TEST(ReferenceCountedSingletonFactory, NonZeroReferenceCountDoesNotDelete) {
+  TestReferenceCountedSingletonFactory factory;
+  rcsf_ptr<MyExistenceWatcher> ptr(&factory);
+  ptr.get();
+  MyExistenceWatcher::delete_called_ = false;
+  DoCreateAndGoOutOfScope(&factory);
+  EXPECT_FALSE(MyExistenceWatcher::delete_called_);
+}
+
+TEST(ReferenceCountedSingletonFactory, ReturnedPointersReferToSameThing) {
+  TestReferenceCountedSingletonFactory factory;
+  rcsf_ptr<MyExistenceWatcher> one(&factory), two(&factory);
+
+  EXPECT_EQ(one.get(), two.get());
+}
+
+TEST(ReferenceCountedSingletonFactory, Release) {
+  TestReferenceCountedSingletonFactory factory;
+
+  rcsf_ptr<MyExistenceWatcher> one(&factory);
+  one.get();
+
+  MyExistenceWatcher::delete_called_ = false;
+  one.release();
+  EXPECT_TRUE(MyExistenceWatcher::delete_called_);
+}
+
+TEST(ReferenceCountedSingletonFactory, GetWithoutRelease) {
+  TestReferenceCountedSingletonFactory factory;
+  rcsf_ptr<MyExistenceWatcher> one(&factory);
+  one.get();
+
+  MyExistenceWatcher::create_called_ = false;
+  one.get();
+  EXPECT_FALSE(MyExistenceWatcher::create_called_);
+}
+
+TEST(ReferenceCountedSingletonFactory, GetAfterRelease) {
+  TestReferenceCountedSingletonFactory factory;
+  rcsf_ptr<MyExistenceWatcher> one(&factory);
+
+  MyExistenceWatcher::create_called_ = false;
+  one.release();
+  one.get();
+  EXPECT_TRUE(MyExistenceWatcher::create_called_);
+}
+
+TEST(ReferenceCountedSingletonFactory, MultipleReleases) {
+  TestReferenceCountedSingletonFactory factory;
+  rcsf_ptr<MyExistenceWatcher> one(&factory), two(&factory);
+
+  MyExistenceWatcher::create_called_ = false;
+  MyExistenceWatcher::delete_called_ = false;
+  one.release();
+  EXPECT_FALSE(MyExistenceWatcher::delete_called_);
+  one.release();
+  EXPECT_FALSE(MyExistenceWatcher::delete_called_);
+  one.release();
+  EXPECT_FALSE(MyExistenceWatcher::delete_called_);
+  one.get();
+  EXPECT_TRUE(MyExistenceWatcher::create_called_);
+}
+
+TEST(ReferenceCountedSingletonFactory, Existentialism) {
+  TestReferenceCountedSingletonFactory factory;
+
+  rcsf_ptr<MyExistenceWatcher> one(&factory);
+
+  MyExistenceWatcher::create_called_ = false;
+  MyExistenceWatcher::delete_called_ = false;
+
+  one.get();
+  EXPECT_TRUE(MyExistenceWatcher::create_called_);
+  one.release();
+  EXPECT_TRUE(MyExistenceWatcher::delete_called_);
+}
+
+}  // namespace talk_base
diff --git a/talk/base/signalthread_unittest.cc b/talk/base/signalthread_unittest.cc
new file mode 100644
index 0000000..91665aa
--- /dev/null
+++ b/talk/base/signalthread_unittest.cc
@@ -0,0 +1,202 @@
+/*
+ * libjingle
+ * Copyright 2004--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/gunit.h"
+#include "talk/base/signalthread.h"
+#include "talk/base/thread.h"
+
+using namespace talk_base;
+
+class SignalThreadTest : public testing::Test, public sigslot::has_slots<> {
+ public:
+  class SlowSignalThread : public SignalThread {
+   public:
+    SlowSignalThread(SignalThreadTest* harness) : harness_(harness) {
+    }
+
+    virtual ~SlowSignalThread() {
+      EXPECT_EQ(harness_->main_thread_, Thread::Current());
+      ++harness_->thread_deleted_;
+    }
+
+    const SignalThreadTest* harness() { return harness_; }
+
+   protected:
+    virtual void OnWorkStart() {
+      ASSERT_TRUE(harness_ != NULL);
+      ++harness_->thread_started_;
+      EXPECT_EQ(harness_->main_thread_, Thread::Current());
+      EXPECT_FALSE(worker()->started());  // not started yet
+    }
+
+    virtual void OnWorkStop() {
+      ++harness_->thread_stopped_;
+      EXPECT_EQ(harness_->main_thread_, Thread::Current());
+      EXPECT_TRUE(worker()->started());  // not stopped yet
+    }
+
+    virtual void OnWorkDone() {
+      ++harness_->thread_done_;
+      EXPECT_EQ(harness_->main_thread_, Thread::Current());
+      EXPECT_TRUE(worker()->started());  // not stopped yet
+    }
+
+    virtual void DoWork() {
+      EXPECT_NE(harness_->main_thread_, Thread::Current());
+      EXPECT_EQ(worker(), Thread::Current());
+      Thread::Current()->socketserver()->Wait(250, false);
+    }
+
+   private:
+    SignalThreadTest* harness_;
+    DISALLOW_EVIL_CONSTRUCTORS(SlowSignalThread);
+  };
+
+  void OnWorkComplete(talk_base::SignalThread* thread) {
+    SlowSignalThread* t = static_cast<SlowSignalThread*>(thread);
+    EXPECT_EQ(t->harness(), this);
+    EXPECT_EQ(main_thread_, Thread::Current());
+
+    ++thread_completed_;
+    if (!called_release_) {
+      thread->Release();
+    }
+  }
+
+  virtual void SetUp() {
+    main_thread_ = Thread::Current();
+    thread_ = new SlowSignalThread(this);
+    thread_->SignalWorkDone.connect(this, &SignalThreadTest::OnWorkComplete);
+    called_release_ = false;
+    thread_started_ = 0;
+    thread_done_ = 0;
+    thread_completed_ = 0;
+    thread_stopped_ = 0;
+    thread_deleted_ = 0;
+  }
+
+  virtual void TearDown() {
+  }
+
+  Thread* main_thread_;
+  SlowSignalThread* thread_;
+  bool called_release_;
+
+  int thread_started_;
+  int thread_done_;
+  int thread_completed_;
+  int thread_stopped_;
+  int thread_deleted_;
+};
+
+class OwnerThread : public Thread, public sigslot::has_slots<> {
+ public:
+  OwnerThread(SignalThreadTest* harness) : harness_(harness) {
+  }
+
+  virtual void Run() {
+    SignalThreadTest::SlowSignalThread* signal_thread =
+        new SignalThreadTest::SlowSignalThread(harness_);
+    signal_thread->SignalWorkDone.connect(this, &OwnerThread::OnWorkDone);
+    signal_thread->Start();
+    Thread::Current()->socketserver()->Wait(100, false);
+    signal_thread->Release();
+  }
+
+  void OnWorkDone(SignalThread* signal_thread) {
+    FAIL() << " This shouldn't get called.";
+  }
+
+ private:
+  SignalThreadTest* harness_;
+  DISALLOW_EVIL_CONSTRUCTORS(OwnerThread);
+};
+
+// Test for when the main thread goes away while the
+// signal thread is still working.  This may happen
+// when shutting down the process.
+TEST_F(SignalThreadTest, OwnerThreadGoesAway) {
+  {
+    scoped_ptr<OwnerThread> owner(new OwnerThread(this));
+    main_thread_ = owner.get();
+    owner->Start();
+    Thread::Current()->socketserver()->Wait(200, false);
+  }
+  // At this point the main thread has gone away.
+  // Give the SignalThread a little time to do its callback,
+  // which will crash if the signal thread doesn't handle
+  // this situation well.
+  Thread::Current()->socketserver()->Wait(500, false);
+}
+
+#define EXPECT_STATE(started, done, completed, stopped, deleted) \
+  EXPECT_EQ(started, thread_started_); \
+  EXPECT_EQ(done, thread_done_); \
+  EXPECT_EQ(completed, thread_completed_); \
+  EXPECT_EQ(stopped, thread_stopped_); \
+  EXPECT_EQ(deleted, thread_deleted_);
+
+TEST_F(SignalThreadTest, ThreadFinishes) {
+  thread_->Start();
+  EXPECT_STATE(1, 0, 0, 0, 0);
+  Thread::SleepMs(500);
+  EXPECT_STATE(1, 0, 0, 0, 0);
+  Thread::Current()->ProcessMessages(0);
+  EXPECT_STATE(1, 1, 1, 0, 1);
+}
+
+TEST_F(SignalThreadTest, ReleasedThreadFinishes) {
+  thread_->Start();
+  EXPECT_STATE(1, 0, 0, 0, 0);
+  thread_->Release();
+  called_release_ = true;
+  EXPECT_STATE(1, 0, 0, 0, 0);
+  Thread::SleepMs(500);
+  EXPECT_STATE(1, 0, 0, 0, 0);
+  Thread::Current()->ProcessMessages(0);
+  EXPECT_STATE(1, 1, 1, 0, 1);
+}
+
+TEST_F(SignalThreadTest, DestroyedThreadCleansUp) {
+  thread_->Start();
+  EXPECT_STATE(1, 0, 0, 0, 0);
+  thread_->Destroy(true);
+  EXPECT_STATE(1, 0, 0, 1, 1);
+  Thread::Current()->ProcessMessages(0);
+  EXPECT_STATE(1, 0, 0, 1, 1);
+}
+
+TEST_F(SignalThreadTest, DeferredDestroyedThreadCleansUp) {
+  thread_->Start();
+  EXPECT_STATE(1, 0, 0, 0, 0);
+  thread_->Destroy(false);
+  EXPECT_STATE(1, 0, 0, 1, 0);
+  Thread::SleepMs(500);
+  EXPECT_STATE(1, 0, 0, 1, 0);
+  Thread::Current()->ProcessMessages(0);
+  EXPECT_STATE(1, 1, 0, 1, 1);
+}
diff --git a/talk/base/socketaddress_unittest.cc b/talk/base/socketaddress_unittest.cc
new file mode 100644
index 0000000..95b0071
--- /dev/null
+++ b/talk/base/socketaddress_unittest.cc
@@ -0,0 +1,209 @@
+/*
+ * libjingle
+ * Copyright 2004--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 POSIX
+#include <netinet/in.h>  // for sockaddr_in
+#endif
+
+#include "talk/base/gunit.h"
+#include "talk/base/socketaddress.h"
+
+namespace talk_base {
+
+TEST(SocketAddressTest, TestDefaultCtor) {
+  SocketAddress addr;
+  EXPECT_FALSE(addr.IsUnresolvedIP());
+  EXPECT_EQ(0U, addr.ip());
+  EXPECT_EQ(0, addr.port());
+  EXPECT_EQ("", addr.hostname());
+  EXPECT_EQ("0.0.0.0:0", addr.ToString());
+}
+
+TEST(SocketAddressTest, TestIPPortCtor) {
+  SocketAddress addr(0x01020304, 5678);
+  EXPECT_FALSE(addr.IsUnresolvedIP());
+  EXPECT_EQ(0x01020304U, addr.ip());
+  EXPECT_EQ(5678, addr.port());
+  EXPECT_EQ("", addr.hostname());
+  EXPECT_EQ("1.2.3.4:5678", addr.ToString());
+}
+
+TEST(SocketAddressTest, TestStringPortCtor) {
+  SocketAddress addr("1.2.3.4", 5678);
+  EXPECT_FALSE(addr.IsUnresolvedIP());
+  EXPECT_EQ(0x01020304U, addr.ip());
+  EXPECT_EQ(5678, addr.port());
+  EXPECT_EQ("1.2.3.4", addr.hostname());
+  EXPECT_EQ("1.2.3.4:5678", addr.ToString());
+}
+
+TEST(SocketAddressTest, TestSpecialStringPortCtor) {
+  // inet_addr doesn't handle this address properly.
+  SocketAddress addr("255.255.255.255", 5678);
+  EXPECT_FALSE(addr.IsUnresolvedIP());
+  EXPECT_EQ(0xFFFFFFFFU, addr.ip());
+  EXPECT_EQ(5678, addr.port());
+  EXPECT_EQ("255.255.255.255", addr.hostname());
+  EXPECT_EQ("255.255.255.255:5678", addr.ToString());
+}
+
+TEST(SocketAddressTest, TestHostnamePortCtor) {
+  SocketAddress addr("a.b.com", 5678);
+  EXPECT_TRUE(addr.IsUnresolvedIP());
+  EXPECT_EQ(0U, addr.ip());
+  EXPECT_EQ(5678, addr.port());
+  EXPECT_EQ("a.b.com", addr.hostname());
+  EXPECT_EQ("a.b.com:5678", addr.ToString());
+}
+
+TEST(SocketAddressTest, TestCopyCtor) {
+  SocketAddress from("1.2.3.4", 5678);
+  SocketAddress addr(from);
+  EXPECT_FALSE(addr.IsUnresolvedIP());
+  EXPECT_EQ(0x01020304U, addr.ip());
+  EXPECT_EQ(5678, addr.port());
+  EXPECT_EQ("1.2.3.4", addr.hostname());
+  EXPECT_EQ("1.2.3.4:5678", addr.ToString());
+}
+
+TEST(SocketAddressTest, TestAssign) {
+  SocketAddress from("1.2.3.4", 5678);
+  SocketAddress addr(0x88888888, 9999);
+  addr = from;
+  EXPECT_FALSE(addr.IsUnresolvedIP());
+  EXPECT_EQ(0x01020304U, addr.ip());
+  EXPECT_EQ(5678, addr.port());
+  EXPECT_EQ("1.2.3.4", addr.hostname());
+  EXPECT_EQ("1.2.3.4:5678", addr.ToString());
+}
+
+TEST(SocketAddressTest, TestSetIPPort) {
+  SocketAddress addr(0x88888888, 9999);
+  addr.SetIP(0x01020304);
+  addr.SetPort(5678);
+  EXPECT_FALSE(addr.IsUnresolvedIP());
+  EXPECT_EQ(0x01020304U, addr.ip());
+  EXPECT_EQ(5678, addr.port());
+  EXPECT_EQ("", addr.hostname());
+  EXPECT_EQ("1.2.3.4:5678", addr.ToString());
+}
+
+TEST(SocketAddressTest, TestSetIPFromString) {
+  SocketAddress addr(0x88888888, 9999);
+  addr.SetIP("1.2.3.4");
+  addr.SetPort(5678);
+  EXPECT_FALSE(addr.IsUnresolvedIP());
+  EXPECT_EQ(0x01020304U, addr.ip());
+  EXPECT_EQ(5678, addr.port());
+  EXPECT_EQ("1.2.3.4", addr.hostname());
+  EXPECT_EQ("1.2.3.4:5678", addr.ToString());
+}
+
+TEST(SocketAddressTest, TestSetIPFromHostname) {
+  SocketAddress addr(0x88888888, 9999);
+  addr.SetIP("a.b.com");
+  addr.SetPort(5678);
+  EXPECT_TRUE(addr.IsUnresolvedIP());
+  EXPECT_EQ(0U, addr.ip());
+  EXPECT_EQ(5678, addr.port());
+  EXPECT_EQ("a.b.com", addr.hostname());
+  EXPECT_EQ("a.b.com:5678", addr.ToString());
+  addr.SetResolvedIP(0x01020304);
+  EXPECT_FALSE(addr.IsUnresolvedIP());
+  EXPECT_EQ(0x01020304U, addr.ip());
+  EXPECT_EQ("a.b.com", addr.hostname());
+  EXPECT_EQ("a.b.com:5678", addr.ToString());
+}
+
+TEST(SocketAddressTest, TestFromString) {
+  SocketAddress addr;
+  EXPECT_TRUE(addr.FromString("1.2.3.4:5678"));
+  EXPECT_FALSE(addr.IsUnresolvedIP());
+  EXPECT_EQ(0x01020304U, addr.ip());
+  EXPECT_EQ(5678, addr.port());
+  EXPECT_EQ("1.2.3.4", addr.hostname());
+  EXPECT_EQ("1.2.3.4:5678", addr.ToString());
+}
+
+TEST(SocketAddressTest, TestFromHostname) {
+  SocketAddress addr;
+  EXPECT_TRUE(addr.FromString("a.b.com:5678"));
+  EXPECT_TRUE(addr.IsUnresolvedIP());
+  EXPECT_EQ(0U, addr.ip());
+  EXPECT_EQ(5678, addr.port());
+  EXPECT_EQ("a.b.com", addr.hostname());
+  EXPECT_EQ("a.b.com:5678", addr.ToString());
+}
+
+TEST(SocketAddressTest, TestToFromSockAddr) {
+  SocketAddress from("1.2.3.4", 5678), addr;
+  sockaddr_in addr_in;
+  from.ToSockAddr(&addr_in);
+  EXPECT_TRUE(addr.FromSockAddr(addr_in));
+  EXPECT_FALSE(addr.IsUnresolvedIP());
+  EXPECT_EQ(0x01020304U, addr.ip());
+  EXPECT_EQ(5678, addr.port());
+  EXPECT_EQ("", addr.hostname());
+  EXPECT_EQ("1.2.3.4:5678", addr.ToString());
+}
+
+TEST(SocketAddressTest, TestToFromBuffer) {
+  SocketAddress from("1.2.3.4", 5678), addr;
+  char buf[8];
+  EXPECT_TRUE(from.Write_(buf, sizeof(buf)));
+  EXPECT_TRUE(addr.Read_(buf, sizeof(buf)));
+  EXPECT_FALSE(addr.IsUnresolvedIP());
+  EXPECT_EQ(0x01020304U, addr.ip());
+  EXPECT_EQ(5678, addr.port());
+  EXPECT_EQ("", addr.hostname());
+  EXPECT_EQ("1.2.3.4:5678", addr.ToString());
+}
+
+TEST(SocketAddressTest, TestGoodResolve) {
+  SocketAddress addr("localhost", 5678);
+  int error;
+  EXPECT_TRUE(addr.IsUnresolvedIP());
+  EXPECT_TRUE(addr.ResolveIP(false, &error));
+  EXPECT_EQ(0, error);
+  EXPECT_FALSE(addr.IsUnresolvedIP());
+  EXPECT_TRUE(addr.IsLoopbackIP());
+  EXPECT_EQ(0x7F000001U, addr.ip());
+  EXPECT_EQ(5678, addr.port());
+  EXPECT_EQ("localhost", addr.hostname());
+  EXPECT_EQ("localhost:5678", addr.ToString());
+}
+
+TEST(SocketAddressTest, TestBadResolve) {
+  SocketAddress addr("address.bad", 5678);
+  int error;
+  EXPECT_TRUE(addr.IsUnresolvedIP());
+  EXPECT_FALSE(addr.ResolveIP(false, &error));
+  EXPECT_NE(0, error);
+  EXPECT_TRUE(addr.IsUnresolvedIP());
+}
+
+}  // namespace talk_base
diff --git a/talk/base/stream_unittest.cc b/talk/base/stream_unittest.cc
new file mode 100644
index 0000000..d0c5718
--- /dev/null
+++ b/talk/base/stream_unittest.cc
@@ -0,0 +1,445 @@
+/*
+ * libjingle
+ * Copyright 2004--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/gunit.h"
+#include "talk/base/stream.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// TestStream
+///////////////////////////////////////////////////////////////////////////////
+
+class TestStream : public StreamInterface {
+public:
+  TestStream() : pos_(0) { }
+
+  virtual StreamState GetState() const { return SS_OPEN; }
+  virtual StreamResult Read(void* buffer, size_t buffer_len,
+                            size_t* read, int* error) {
+    unsigned char* uc_buffer = static_cast<unsigned char*>(buffer);
+    for (size_t i=0; i<buffer_len; ++i) {
+      uc_buffer[i] = pos_++;
+    }
+    if (read)
+      *read = buffer_len;
+    return SR_SUCCESS;
+  }
+  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() { }
+  virtual bool SetPosition(size_t position) {
+    pos_ = position;
+    return true;
+  }
+  virtual bool GetPosition(size_t* position) const {
+    if (position) *position = pos_;
+    return true;
+  }
+  virtual bool GetSize(size_t* size) const {
+    return false;
+  }
+  virtual bool GetAvailable(size_t* size) const {
+    return false;
+  }
+
+private:
+  unsigned char pos_;
+};
+
+bool VerifyTestBuffer(unsigned char* buffer, size_t len,
+                      unsigned char value) {
+  bool passed = true;
+  for (size_t i=0; i<len; ++i) {
+    if (buffer[i] != value++) {
+      passed = false;
+      break;
+    }
+  }
+  // Ensure that we don't pass again without re-writing
+  memset(buffer, 0, len);
+  return passed;
+}
+
+void SeekTest(StreamInterface* stream, const unsigned char value) {
+  size_t bytes;
+  unsigned char buffer[13] = { 0 };
+  const size_t kBufSize = sizeof(buffer);
+
+  EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_SUCCESS);
+  EXPECT_EQ(bytes, kBufSize);
+  EXPECT_TRUE(VerifyTestBuffer(buffer, kBufSize, value));
+  EXPECT_TRUE(stream->GetPosition(&bytes));
+  EXPECT_EQ(13U, bytes);
+
+  EXPECT_TRUE(stream->SetPosition(7));
+
+  EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_SUCCESS);
+  EXPECT_EQ(bytes, kBufSize);
+  EXPECT_TRUE(VerifyTestBuffer(buffer, kBufSize, value + 7));
+  EXPECT_TRUE(stream->GetPosition(&bytes));
+  EXPECT_EQ(20U, bytes);
+}
+
+TEST(StreamSegment, TranslatesPosition) {
+  TestStream* test = new TestStream;
+  // Verify behavior of original stream
+  SeekTest(test, 0);
+  StreamSegment* segment = new StreamSegment(test);
+  // Verify behavior of adapted stream (all values offset by 20)
+  SeekTest(segment, 20);
+  delete segment;
+}
+
+TEST(StreamSegment, SupportsArtificialTermination) {
+  TestStream* test = new TestStream;
+
+  size_t bytes;
+  unsigned char buffer[5000] = { 0 };
+  const size_t kBufSize = sizeof(buffer);
+
+  {
+    StreamInterface* stream = test;
+
+    // Read a lot of bytes
+    EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_SUCCESS);
+    EXPECT_EQ(bytes, kBufSize);
+    EXPECT_TRUE(VerifyTestBuffer(buffer, kBufSize, 0));
+
+    // Test seeking far ahead
+    EXPECT_TRUE(stream->SetPosition(12345));
+
+    // Read a bunch more bytes
+    EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_SUCCESS);
+    EXPECT_EQ(bytes, kBufSize);
+    EXPECT_TRUE(VerifyTestBuffer(buffer, kBufSize, 12345 % 256));
+  }
+
+  // Create a segment of test stream in range [100,600)
+  EXPECT_TRUE(test->SetPosition(100));
+  StreamSegment* segment = new StreamSegment(test, 500);
+
+  {
+    StreamInterface* stream = segment;
+
+    EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_SUCCESS);
+    EXPECT_EQ(500U, bytes);
+    EXPECT_TRUE(VerifyTestBuffer(buffer, 500, 100));
+    EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_EOS);
+
+    // Test seeking past "end" of stream
+    EXPECT_FALSE(stream->SetPosition(12345));
+    EXPECT_FALSE(stream->SetPosition(501));
+
+    // Test seeking to end (edge case)
+    EXPECT_TRUE(stream->SetPosition(500));
+    EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_EOS);
+
+    // Test seeking to start
+    EXPECT_TRUE(stream->SetPosition(0));
+    EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_SUCCESS);
+    EXPECT_EQ(500U, bytes);
+    EXPECT_TRUE(VerifyTestBuffer(buffer, 500, 100));
+    EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_EOS);
+  }
+
+  delete segment;
+}
+
+TEST(FifoBufferTest, TestAll) {
+  const size_t kSize = 16;
+  const char in[kSize * 2 + 1] = "0123456789ABCDEFGHIJKLMNOPQRSTUV";
+  char out[kSize * 2];
+  void* p;
+  const void* q;
+  size_t bytes;
+  FifoBuffer buf(kSize);
+  StreamInterface* stream = &buf;
+
+  // Test assumptions about base state
+  EXPECT_EQ(SS_OPEN, stream->GetState());
+  EXPECT_EQ(SR_BLOCK, stream->Read(out, kSize, &bytes, NULL));
+  EXPECT_TRUE(NULL != stream->GetReadData(&bytes));
+  EXPECT_EQ((size_t)0, bytes);
+  stream->ConsumeReadData(0);
+  EXPECT_TRUE(NULL != stream->GetWriteBuffer(&bytes));
+  EXPECT_EQ(kSize, bytes);
+  stream->ConsumeWriteBuffer(0);
+
+  // Try a full write
+  EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize, &bytes, NULL));
+  EXPECT_EQ(kSize, bytes);
+
+  // Try a write that should block
+  EXPECT_EQ(SR_BLOCK, stream->Write(in, kSize, &bytes, NULL));
+
+  // Try a full read
+  EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize, &bytes, NULL));
+  EXPECT_EQ(kSize, bytes);
+  EXPECT_EQ(0, memcmp(in, out, kSize));
+
+  // Try a read that should block
+  EXPECT_EQ(SR_BLOCK, stream->Read(out, kSize, &bytes, NULL));
+
+  // Try a too-big write
+  EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize * 2, &bytes, NULL));
+  EXPECT_EQ(bytes, kSize);
+
+  // Try a too-big read
+  EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize * 2, &bytes, NULL));
+  EXPECT_EQ(kSize, bytes);
+  EXPECT_EQ(0, memcmp(in, out, kSize));
+
+  // Try some small writes and reads
+  EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize / 2, &bytes, NULL));
+  EXPECT_EQ(kSize / 2, bytes);
+  EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL));
+  EXPECT_EQ(kSize / 2, bytes);
+  EXPECT_EQ(0, memcmp(in, out, kSize / 2));
+  EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize / 2, &bytes, NULL));
+  EXPECT_EQ(kSize / 2, bytes);
+  EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize / 2, &bytes, NULL));
+  EXPECT_EQ(kSize / 2, bytes);
+  EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL));
+  EXPECT_EQ(kSize / 2, bytes);
+  EXPECT_EQ(0, memcmp(in, out, kSize / 2));
+  EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL));
+  EXPECT_EQ(kSize / 2, bytes);
+  EXPECT_EQ(0, memcmp(in, out, kSize / 2));
+
+  // Try wraparound reads and writes in the following pattern
+  // WWWWWWWWWWWW.... 0123456789AB....
+  // RRRRRRRRXXXX.... ........89AB....
+  // WWWW....XXXXWWWW 4567....89AB0123
+  // XXXX....RRRRXXXX 4567........0123
+  // XXXXWWWWWWWWXXXX 4567012345670123
+  // RRRRXXXXXXXXRRRR ....01234567....
+  // ....RRRRRRRR.... ................
+  EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize * 3 / 4, &bytes, NULL));
+  EXPECT_EQ(kSize * 3 / 4, bytes);
+  EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL));
+  EXPECT_EQ(kSize / 2, bytes);
+  EXPECT_EQ(0, memcmp(in, out, kSize / 2));
+  EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize / 2, &bytes, NULL));
+  EXPECT_EQ(kSize / 2, bytes);
+  EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 4, &bytes, NULL));
+  EXPECT_EQ(kSize / 4 , bytes);
+  EXPECT_EQ(0, memcmp(in + kSize / 2, out, kSize / 4));
+  EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize / 2, &bytes, NULL));
+  EXPECT_EQ(kSize / 2, bytes);
+  EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL));
+  EXPECT_EQ(kSize / 2 , bytes);
+  EXPECT_EQ(0, memcmp(in, out, kSize / 2));
+  EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL));
+  EXPECT_EQ(kSize / 2 , bytes);
+  EXPECT_EQ(0, memcmp(in, out, kSize / 2));
+
+  // Use GetWriteBuffer to reset the read_position for the next tests
+  stream->GetWriteBuffer(&bytes);
+  stream->ConsumeWriteBuffer(0);
+
+  // Try using GetReadData to do a full read
+  EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize, &bytes, NULL));
+  q = stream->GetReadData(&bytes);
+  EXPECT_TRUE(NULL != q);
+  EXPECT_EQ(kSize, bytes);
+  EXPECT_EQ(0, memcmp(q, in, kSize));
+  stream->ConsumeReadData(kSize);
+  EXPECT_EQ(SR_BLOCK, stream->Read(out, kSize, &bytes, NULL));
+
+  // Try using GetReadData to do some small reads
+  EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize, &bytes, NULL));
+  q = stream->GetReadData(&bytes);
+  EXPECT_TRUE(NULL != q);
+  EXPECT_EQ(kSize, bytes);
+  EXPECT_EQ(0, memcmp(q, in, kSize / 2));
+  stream->ConsumeReadData(kSize / 2);
+  q = stream->GetReadData(&bytes);
+  EXPECT_TRUE(NULL != q);
+  EXPECT_EQ(kSize / 2, bytes);
+  EXPECT_EQ(0, memcmp(q, in + kSize / 2, kSize / 2));
+  stream->ConsumeReadData(kSize / 2);
+  EXPECT_EQ(SR_BLOCK, stream->Read(out, kSize, &bytes, NULL));
+
+  // Try using GetReadData in a wraparound case
+  // WWWWWWWWWWWWWWWW 0123456789ABCDEF
+  // RRRRRRRRRRRRXXXX ............CDEF
+  // WWWWWWWW....XXXX 01234567....CDEF
+  // ............RRRR 01234567........
+  // RRRRRRRR........ ................
+  EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize, &bytes, NULL));
+  EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize * 3 / 4, &bytes, NULL));
+  EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize / 2, &bytes, NULL));
+  q = stream->GetReadData(&bytes);
+  EXPECT_TRUE(NULL != q);
+  EXPECT_EQ(kSize / 4, bytes);
+  EXPECT_EQ(0, memcmp(q, in + kSize * 3 / 4, kSize / 4));
+  stream->ConsumeReadData(kSize / 4);
+  q = stream->GetReadData(&bytes);
+  EXPECT_TRUE(NULL != q);
+  EXPECT_EQ(kSize / 2, bytes);
+  EXPECT_EQ(0, memcmp(q, in, kSize / 2));
+  stream->ConsumeReadData(kSize / 2);
+
+  // Use GetWriteBuffer to reset the read_position for the next tests
+  stream->GetWriteBuffer(&bytes);
+  stream->ConsumeWriteBuffer(0);
+
+  // Try using GetWriteBuffer to do a full write
+  p = stream->GetWriteBuffer(&bytes);
+  EXPECT_TRUE(NULL != p);
+  EXPECT_EQ(kSize, bytes);
+  memcpy(p, in, kSize);
+  stream->ConsumeWriteBuffer(kSize);
+  EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize, &bytes, NULL));
+  EXPECT_EQ(kSize, bytes);
+  EXPECT_EQ(0, memcmp(in, out, kSize));
+
+  // Try using GetWriteBuffer to do some small writes
+  p = stream->GetWriteBuffer(&bytes);
+  EXPECT_TRUE(NULL != p);
+  EXPECT_EQ(kSize, bytes);
+  memcpy(p, in, kSize / 2);
+  stream->ConsumeWriteBuffer(kSize / 2);
+  p = stream->GetWriteBuffer(&bytes);
+  EXPECT_TRUE(NULL != p);
+  EXPECT_EQ(kSize / 2, bytes);
+  memcpy(p, in + kSize / 2, kSize / 2);
+  stream->ConsumeWriteBuffer(kSize / 2);
+  EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize, &bytes, NULL));
+  EXPECT_EQ(kSize, bytes);
+  EXPECT_EQ(0, memcmp(in, out, kSize));
+
+  // Try using GetWriteBuffer in a wraparound case
+  // WWWWWWWWWWWW.... 0123456789AB....
+  // RRRRRRRRXXXX.... ........89AB....
+  // ........XXXXWWWW ........89AB0123
+  // WWWW....XXXXXXXX 4567....89AB0123
+  // RRRR....RRRRRRRR ................
+  EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize * 3 / 4, &bytes, NULL));
+  EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL));
+  p = stream->GetWriteBuffer(&bytes);
+  EXPECT_TRUE(NULL != p);
+  EXPECT_EQ(kSize / 4, bytes);
+  memcpy(p, in, kSize / 4);
+  stream->ConsumeWriteBuffer(kSize / 4);
+  p = stream->GetWriteBuffer(&bytes);
+  EXPECT_TRUE(NULL != p);
+  EXPECT_EQ(kSize / 2, bytes);
+  memcpy(p, in + kSize / 4, kSize / 4);
+  stream->ConsumeWriteBuffer(kSize / 4);
+  EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize * 3 / 4, &bytes, NULL));
+  EXPECT_EQ(kSize * 3 / 4, bytes);
+  EXPECT_EQ(0, memcmp(in + kSize / 2, out, kSize / 4));
+  EXPECT_EQ(0, memcmp(in, out + kSize / 4, kSize / 4));
+
+  // Check that the stream is now empty
+  EXPECT_EQ(SR_BLOCK, stream->Read(out, kSize, &bytes, NULL));
+
+  // Try growing the buffer
+  EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize, &bytes, NULL));
+  EXPECT_EQ(kSize, bytes);
+  EXPECT_TRUE(buf.SetCapacity(kSize * 2));
+  EXPECT_EQ(SR_SUCCESS, stream->Write(in + kSize, kSize, &bytes, NULL));
+  EXPECT_EQ(kSize, bytes);
+  EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize * 2, &bytes, NULL));
+  EXPECT_EQ(kSize * 2, bytes);
+  EXPECT_EQ(0, memcmp(in, out, kSize * 2));
+
+  // Try shrinking the buffer
+  EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize, &bytes, NULL));
+  EXPECT_EQ(kSize, bytes);
+  EXPECT_TRUE(buf.SetCapacity(kSize));
+  EXPECT_EQ(SR_BLOCK, stream->Write(in, kSize, &bytes, NULL));
+  EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize, &bytes, NULL));
+  EXPECT_EQ(kSize, bytes);
+  EXPECT_EQ(0, memcmp(in, out, kSize));
+
+  // Write to the stream, close it, read the remaining bytes
+  EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize / 2, &bytes, NULL));
+  stream->Close();
+  EXPECT_EQ(SS_CLOSED, stream->GetState());
+  EXPECT_EQ(SR_EOS, stream->Write(in, kSize / 2, &bytes, NULL));
+  EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL));
+  EXPECT_EQ(0, memcmp(in, out, kSize / 2));
+  EXPECT_EQ(SR_EOS, stream->Read(out, kSize / 2, &bytes, NULL));
+}
+
+TEST(FifoBufferTest, WriteOffsetAndReadOffset) {
+  const size_t kSize = 16;
+  const char in[kSize * 2 + 1] = "0123456789ABCDEFGHIJKLMNOPQRSTUV";
+  char out[kSize * 2];
+  FifoBuffer buf(kSize);
+
+  // Write 14 bytes.
+  EXPECT_EQ(SR_SUCCESS, buf.Write(in, 14, NULL, NULL));
+
+  // Make sure data is in |buf|.
+  size_t buffered;
+  EXPECT_TRUE(buf.GetBuffered(&buffered));
+  EXPECT_EQ(14u, buffered);
+
+  // Read 10 bytes.
+  buf.ConsumeReadData(10);
+
+  // There should be now 12 bytes of available space.
+  size_t remaining;
+  EXPECT_TRUE(buf.GetWriteRemaining(&remaining));
+  EXPECT_EQ(12u, remaining);
+
+  // Write at offset 12, this should fail.
+  EXPECT_EQ(SR_BLOCK, buf.WriteOffset(in, 10, 12, NULL));
+
+  // Write 8 bytes at offset 4, this wraps around the buffer.
+  EXPECT_EQ(SR_SUCCESS, buf.WriteOffset(in, 8, 4, NULL));
+
+  // Number of available space remains the same until we call
+  // ConsumeWriteBuffer().
+  EXPECT_TRUE(buf.GetWriteRemaining(&remaining));
+  EXPECT_EQ(12u, remaining);
+  buf.ConsumeWriteBuffer(12);
+
+  // There's 4 bytes bypassed and 4 bytes no read so skip them and verify the
+  // 8 bytes written.
+  size_t read;
+  EXPECT_EQ(SR_SUCCESS, buf.ReadOffset(out, 8, 8, &read));
+  EXPECT_EQ(8u, read);
+  EXPECT_EQ(0, memcmp(out, in, 8));
+
+  // There should still be 16 bytes available for reading.
+  EXPECT_TRUE(buf.GetBuffered(&buffered));
+  EXPECT_EQ(16u, buffered);
+
+  // Read at offset 16, this should fail since we don't have that much data.
+  EXPECT_EQ(SR_BLOCK, buf.ReadOffset(out, 10, 16, NULL));
+}
+
+} // namespace talk_base
diff --git a/talk/base/stringencode_unittest.cc b/talk/base/stringencode_unittest.cc
new file mode 100644
index 0000000..fc1f481
--- /dev/null
+++ b/talk/base/stringencode_unittest.cc
@@ -0,0 +1,204 @@
+/*
+ * libjingle
+ * Copyright 2004--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/common.h"
+#include "talk/base/gunit.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
+
+namespace talk_base {
+
+TEST(utf8_encode, EncodeDecode) {
+  const struct Utf8Test {
+    const char* encoded;
+    size_t encsize, enclen;
+    unsigned long decoded;
+  } kTests[] = {
+    { "a    ",             5, 1, 'a' },
+    { "\x7F    ",          5, 1, 0x7F },
+    { "\xC2\x80   ",       5, 2, 0x80 },
+    { "\xDF\xBF   ",       5, 2, 0x7FF },
+    { "\xE0\xA0\x80  ",    5, 3, 0x800 },
+    { "\xEF\xBF\xBF  ",    5, 3, 0xFFFF },
+    { "\xF0\x90\x80\x80 ", 5, 4, 0x10000 },
+    { "\xF0\x90\x80\x80 ", 3, 0, 0x10000 },
+    { "\xF0\xF0\x80\x80 ", 5, 0, 0 },
+    { "\xF0\x90\x80  ",    5, 0, 0 },
+    { "\x90\x80\x80  ",    5, 0, 0 },
+    { NULL, 0, 0 },
+  };
+  for (size_t i=0; kTests[i].encoded; ++i) {
+    unsigned long val = 0;
+    ASSERT_EQ(kTests[i].enclen, utf8_decode(kTests[i].encoded,
+                                            kTests[i].encsize,
+                                            &val));
+    unsigned long result = (kTests[i].enclen == 0) ? 0 : kTests[i].decoded;
+    ASSERT_EQ(result, val);
+
+    if (kTests[i].decoded == 0) {
+      // Not an interesting encoding test case
+      continue;
+    }
+
+    char buffer[5];
+    memset(buffer, 0x01, ARRAY_SIZE(buffer));
+    ASSERT_EQ(kTests[i].enclen, utf8_encode(buffer,
+                                            kTests[i].encsize,
+                                            kTests[i].decoded));
+    ASSERT_TRUE(memcmp(buffer, kTests[i].encoded, kTests[i].enclen) == 0);
+    // Make sure remainder of buffer is unchanged
+    ASSERT_TRUE(memory_check(buffer + kTests[i].enclen,
+                             0x1,
+                             ARRAY_SIZE(buffer) - kTests[i].enclen));
+  }
+}
+
+// TODO: hex_encode unittest
+
+// Tests counting substrings.
+TEST(tokenizeTest, CountSubstrings) {
+  std::vector<std::string> fields;
+
+  EXPECT_EQ(5ul, tokenize("one two three four five", ' ', &fields));
+  fields.clear();
+  EXPECT_EQ(1ul, tokenize("one", ' ', &fields));
+
+  // Extra spaces should be ignored.
+  fields.clear();
+  EXPECT_EQ(5ul, tokenize("  one    two  three    four five  ", ' ', &fields));
+  fields.clear();
+  EXPECT_EQ(1ul, tokenize("  one  ", ' ', &fields));
+  fields.clear();
+  EXPECT_EQ(0ul, tokenize(" ", ' ', &fields));
+}
+
+// Tests comparing substrings.
+TEST(tokenizeTest, CompareSubstrings) {
+  std::vector<std::string> fields;
+
+  tokenize("find middle one", ' ', &fields);
+  ASSERT_EQ(3ul, fields.size());
+  ASSERT_STREQ("middle", fields.at(1).c_str());
+  fields.clear();
+
+  // Extra spaces should be ignored.
+  tokenize("  find   middle  one    ", ' ', &fields);
+  ASSERT_EQ(3ul, fields.size());
+  ASSERT_STREQ("middle", fields.at(1).c_str());
+  fields.clear();
+  tokenize(" ", ' ', &fields);
+  ASSERT_EQ(0ul, fields.size());
+}
+
+TEST(tokenizeTest, TokenizeAppend) {
+  ASSERT_EQ(0ul, tokenize_append("A B C", ' ', NULL));
+
+  std::vector<std::string> fields;
+
+  tokenize_append("A B C", ' ', &fields);
+  ASSERT_EQ(3ul, fields.size());
+  ASSERT_STREQ("B", fields.at(1).c_str());
+
+  tokenize_append("D E", ' ', &fields);
+  ASSERT_EQ(5ul, fields.size());
+  ASSERT_STREQ("B", fields.at(1).c_str());
+  ASSERT_STREQ("E", fields.at(4).c_str());
+}
+
+TEST(tokenizeTest, TokenizeWithMarks) {
+  ASSERT_EQ(0ul, tokenize("D \"A B", ' ', '(', ')', NULL));
+
+  std::vector<std::string> fields;
+  tokenize("A B C", ' ', '"', '"', &fields);
+  ASSERT_EQ(3ul, fields.size());
+  ASSERT_STREQ("C", fields.at(2).c_str());
+
+  tokenize("\"A B\" C", ' ', '"', '"', &fields);
+  ASSERT_EQ(2ul, fields.size());
+  ASSERT_STREQ("A B", fields.at(0).c_str());
+
+  tokenize("D \"A B\" C", ' ', '"', '"', &fields);
+  ASSERT_EQ(3ul, fields.size());
+  ASSERT_STREQ("D", fields.at(0).c_str());
+  ASSERT_STREQ("A B", fields.at(1).c_str());
+
+  tokenize("D \"A B\" C \"E F\"", ' ', '"', '"', &fields);
+  ASSERT_EQ(4ul, fields.size());
+  ASSERT_STREQ("D", fields.at(0).c_str());
+  ASSERT_STREQ("A B", fields.at(1).c_str());
+  ASSERT_STREQ("E F", fields.at(3).c_str());
+
+  // No matching marks.
+  tokenize("D \"A B", ' ', '"', '"', &fields);
+  ASSERT_EQ(3ul, fields.size());
+  ASSERT_STREQ("D", fields.at(0).c_str());
+  ASSERT_STREQ("\"A", fields.at(1).c_str());
+
+  tokenize("D (A B) C (E F) G", ' ', '(', ')', &fields);
+  ASSERT_EQ(5ul, fields.size());
+  ASSERT_STREQ("D", fields.at(0).c_str());
+  ASSERT_STREQ("A B", fields.at(1).c_str());
+  ASSERT_STREQ("E F", fields.at(3).c_str());
+}
+
+// Tests counting substrings.
+TEST(splitTest, CountSubstrings) {
+  std::vector<std::string> fields;
+
+  EXPECT_EQ(5ul, split("one,two,three,four,five", ',', &fields));
+  fields.clear();
+  EXPECT_EQ(1ul, split("one", ',', &fields));
+
+  // Empty fields between commas count.
+  fields.clear();
+  EXPECT_EQ(5ul, split("one,,three,four,five", ',', &fields));
+  fields.clear();
+  EXPECT_EQ(3ul, split(",three,", ',', &fields));
+  fields.clear();
+  EXPECT_EQ(1ul, split("", ',', &fields));
+}
+
+// Tests comparing substrings.
+TEST(splitTest, CompareSubstrings) {
+  std::vector<std::string> fields;
+
+  split("find,middle,one", ',', &fields);
+  ASSERT_EQ(3ul, fields.size());
+  ASSERT_STREQ("middle", fields.at(1).c_str());
+  fields.clear();
+
+  // Empty fields between commas count.
+  split("find,,middle,one", ',', &fields);
+  ASSERT_EQ(4ul, fields.size());
+  ASSERT_STREQ("middle", fields.at(2).c_str());
+  fields.clear();
+  split("", ',', &fields);
+  ASSERT_EQ(1ul, fields.size());
+  ASSERT_STREQ("", fields.at(0).c_str());
+}
+
+} // namespace talk_base
diff --git a/talk/base/stringutils_unittest.cc b/talk/base/stringutils_unittest.cc
new file mode 100644
index 0000000..5611869
--- /dev/null
+++ b/talk/base/stringutils_unittest.cc
@@ -0,0 +1,126 @@
+/*
+ * libjingle
+ * Copyright 2004--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/gunit.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/common.h"
+
+namespace talk_base {
+
+// Tests for string_match().
+
+TEST(string_matchTest, Matches) {
+  EXPECT_TRUE( string_match("A.B.C.D", "a.b.c.d"));
+  EXPECT_TRUE( string_match("www.TEST.GOOGLE.COM", "www.*.com"));
+  EXPECT_TRUE( string_match("127.0.0.1",  "12*.0.*1"));
+  EXPECT_TRUE( string_match("127.1.0.21", "12*.0.*1"));
+  EXPECT_FALSE(string_match("127.0.0.0",  "12*.0.*1"));
+  EXPECT_FALSE(string_match("127.0.0.0",  "12*.0.*1"));
+  EXPECT_FALSE(string_match("127.1.1.21", "12*.0.*1"));
+}
+
+// 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.
+
+#ifdef WIN32
+
+// Tests for ascii_string_compare().
+
+// Tests NULL input.
+TEST(ascii_string_compareTest, NullInput) {
+  // The following results in an access violation in
+  // ascii_string_compare.  Is this a bug or by design?  stringutils.h
+  // should document the expected behavior in this case.
+
+  // EXPECT_EQ(0, ascii_string_compare(NULL, NULL, 1, identity));
+}
+
+// Tests comparing two strings of different lengths.
+TEST(ascii_string_compareTest, DifferentLengths) {
+  EXPECT_EQ(-1, ascii_string_compare(L"Test", "Test1", 5, identity));
+}
+
+// Tests the case where the buffer size is smaller than the string
+// lengths.
+TEST(ascii_string_compareTest, SmallBuffer) {
+  EXPECT_EQ(0, ascii_string_compare(L"Test", "Test1", 3, identity));
+}
+
+// Tests the case where the buffer is not full.
+TEST(ascii_string_compareTest, LargeBuffer) {
+  EXPECT_EQ(0, ascii_string_compare(L"Test", "Test", 10, identity));
+}
+
+// Tests comparing two eqaul strings.
+TEST(ascii_string_compareTest, Equal) {
+  EXPECT_EQ(0, ascii_string_compare(L"Test", "Test", 5, identity));
+  EXPECT_EQ(0, ascii_string_compare(L"TeSt", "tEsT", 5, tolowercase));
+}
+
+// Tests comparing a smller string to a larger one.
+TEST(ascii_string_compareTest, LessThan) {
+  EXPECT_EQ(-1, ascii_string_compare(L"abc", "abd", 4, identity));
+  EXPECT_EQ(-1, ascii_string_compare(L"ABC", "abD", 5, tolowercase));
+}
+
+// Tests comparing a larger string to a smaller one.
+TEST(ascii_string_compareTest, GreaterThan) {
+  EXPECT_EQ(1, ascii_string_compare(L"xyz", "xy", 5, identity));
+  EXPECT_EQ(1, ascii_string_compare(L"abc", "ABB", 5, tolowercase));
+}
+#endif  // WIN32
+
+TEST(string_trim_Test, Trimming) {
+  EXPECT_EQ("temp", string_trim("\n\r\t temp \n\r\t"));
+  EXPECT_EQ("temp\n\r\t temp", string_trim(" temp\n\r\t temp "));
+  EXPECT_EQ("temp temp", string_trim("temp temp"));
+  EXPECT_EQ("", string_trim(" \r\n\t"));
+  EXPECT_EQ("", string_trim(""));
+}
+
+TEST(string_startsTest, StartsWith) {
+  EXPECT_TRUE(starts_with("foobar", "foo"));
+  EXPECT_TRUE(starts_with("foobar", "foobar"));
+  EXPECT_TRUE(starts_with("foobar", ""));
+  EXPECT_TRUE(starts_with("", ""));
+  EXPECT_FALSE(starts_with("foobar", "bar"));
+  EXPECT_FALSE(starts_with("foobar", "foobarbaz"));
+  EXPECT_FALSE(starts_with("", "f"));
+}
+
+TEST(string_endsTest, EndsWith) {
+  EXPECT_TRUE(ends_with("foobar", "bar"));
+  EXPECT_TRUE(ends_with("foobar", "foobar"));
+  EXPECT_TRUE(ends_with("foobar", ""));
+  EXPECT_TRUE(ends_with("", ""));
+  EXPECT_FALSE(ends_with("foobar", "foo"));
+  EXPECT_FALSE(ends_with("foobar", "foobarbaz"));
+  EXPECT_FALSE(ends_with("", "f"));
+}
+
+} // namespace talk_base
diff --git a/talk/base/task_unittest.cc b/talk/base/task_unittest.cc
new file mode 100644
index 0000000..076ff6f
--- /dev/null
+++ b/talk/base/task_unittest.cc
@@ -0,0 +1,562 @@
+/*
+ * libjingle
+ * Copyright 2004--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 POSIX
+#include <sys/time.h>
+#endif  // POSIX
+
+// TODO: Remove this once the cause of sporadic failures in these
+// tests is tracked down.
+#include <iostream>
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#endif  // WIN32
+
+#include "talk/base/common.h"
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/task.h"
+#include "talk/base/taskrunner.h"
+#include "talk/base/thread.h"
+#include "talk/base/time.h"
+
+namespace talk_base {
+
+static int64 GetCurrentTime() {
+  return static_cast<int64>(Time()) * 10000;
+}
+
+// feel free to change these numbers.  Note that '0' won't work, though
+#define STUCK_TASK_COUNT 5
+#define HAPPY_TASK_COUNT 20
+
+// this is a generic timeout task which, when it signals timeout, will
+// include the unique ID of the task in the signal (we don't use this
+// in production code because we haven't yet had occasion to generate
+// an array of the same types of task)
+
+class IdTimeoutTask : public Task, public sigslot::has_slots<> {
+ public:
+  explicit IdTimeoutTask(TaskParent *parent) : Task(parent) {
+    SignalTimeout.connect(this, &IdTimeoutTask::OnLocalTimeout);
+  }
+
+  sigslot::signal1<const int> SignalTimeoutId;
+  sigslot::signal1<const int> SignalDoneId;
+
+  virtual int ProcessStart() {
+    return STATE_RESPONSE;
+  }
+
+  void OnLocalTimeout() {
+    SignalTimeoutId(unique_id());
+  }
+
+ protected:
+  virtual void Stop() {
+    SignalDoneId(unique_id());
+    Task::Stop();
+  }
+};
+
+class StuckTask : public IdTimeoutTask {
+ public:
+  explicit StuckTask(TaskParent *parent) : IdTimeoutTask(parent) {}
+  virtual int ProcessStart() {
+    return STATE_BLOCKED;
+  }
+};
+
+class HappyTask : public IdTimeoutTask {
+ public:
+  explicit HappyTask(TaskParent *parent) : IdTimeoutTask(parent) {
+    time_to_perform_ = rand() % (STUCK_TASK_COUNT / 2);
+  }
+  virtual int ProcessStart() {
+    if (ElapsedTime() > (time_to_perform_ * 1000 * 10000))
+      return STATE_RESPONSE;
+    else
+      return STATE_BLOCKED;
+  }
+
+ private:
+  int time_to_perform_;
+};
+
+// simple implementation of a task runner which uses Windows'
+// GetSystemTimeAsFileTime() to get the current clock ticks
+
+class MyTaskRunner : public TaskRunner {
+ public:
+  virtual void WakeTasks() { RunTasks(); }
+  virtual int64 CurrentTime() {
+    return GetCurrentTime();
+  }
+
+  bool timeout_change() const {
+    return timeout_change_;
+  }
+
+  void clear_timeout_change() {
+    timeout_change_ = false;
+  }
+ protected:
+  virtual void OnTimeoutChange() {
+    timeout_change_ = true;
+  }
+  bool timeout_change_;
+};
+
+//
+// this unit test is primarily concerned (for now) with the timeout
+// functionality in tasks.  It works as follows:
+//
+//   * Create a bunch of tasks, some "stuck" (ie., guaranteed to timeout)
+//     and some "happy" (will immediately finish).
+//   * Set the timeout on the "stuck" tasks to some number of seconds between
+//     1 and the number of stuck tasks
+//   * Start all the stuck & happy tasks in random order
+//   * Wait "number of stuck tasks" seconds and make sure everything timed out
+
+class TaskTest : public sigslot::has_slots<> {
+ public:
+  TaskTest() {}
+
+  // no need to delete any tasks; the task runner owns them
+  ~TaskTest() {}
+
+  void Start() {
+    // create and configure tasks
+    for (int i = 0; i < STUCK_TASK_COUNT; ++i) {
+      stuck_[i].task_ = new StuckTask(&task_runner_);
+      stuck_[i].task_->SignalTimeoutId.connect(this,
+                                               &TaskTest::OnTimeoutStuck);
+      stuck_[i].timed_out_ = false;
+      stuck_[i].xlat_ = stuck_[i].task_->unique_id();
+      stuck_[i].task_->set_timeout_seconds(i + 1);
+      LOG(LS_INFO) << "Task " << stuck_[i].xlat_ << " created with timeout "
+                   << stuck_[i].task_->timeout_seconds();
+    }
+
+    for (int i = 0; i < HAPPY_TASK_COUNT; ++i) {
+      happy_[i].task_ = new HappyTask(&task_runner_);
+      happy_[i].task_->SignalTimeoutId.connect(this,
+                                               &TaskTest::OnTimeoutHappy);
+      happy_[i].task_->SignalDoneId.connect(this,
+                                            &TaskTest::OnDoneHappy);
+      happy_[i].timed_out_ = false;
+      happy_[i].xlat_ = happy_[i].task_->unique_id();
+    }
+
+    // start all the tasks in random order
+    int stuck_index = 0;
+    int happy_index = 0;
+    for (int i = 0; i < STUCK_TASK_COUNT + HAPPY_TASK_COUNT; ++i) {
+      if ((stuck_index < STUCK_TASK_COUNT) &&
+          (happy_index < HAPPY_TASK_COUNT)) {
+        if (rand() % 2 == 1) {
+          stuck_[stuck_index++].task_->Start();
+        } else {
+          happy_[happy_index++].task_->Start();
+        }
+      } else if (stuck_index < STUCK_TASK_COUNT) {
+        stuck_[stuck_index++].task_->Start();
+      } else {
+        happy_[happy_index++].task_->Start();
+      }
+    }
+
+    for (int i = 0; i < STUCK_TASK_COUNT; ++i) {
+      std::cout << "Stuck task #" << i << " timeout is " <<
+          stuck_[i].task_->timeout_seconds() << " at " <<
+          stuck_[i].task_->timeout_time() << std::endl;
+    }
+
+    // just a little self-check to make sure we started all the tasks
+    ASSERT_EQ(STUCK_TASK_COUNT, stuck_index);
+    ASSERT_EQ(HAPPY_TASK_COUNT, happy_index);
+
+    // run the unblocked tasks
+    LOG(LS_INFO) << "Running tasks";
+    task_runner_.RunTasks();
+
+    std::cout << "Start time is " << GetCurrentTime() << std::endl;
+
+    // give all the stuck tasks time to timeout
+    for (int i = 0; !task_runner_.AllChildrenDone() && i < STUCK_TASK_COUNT;
+         ++i) {
+      Thread::Current()->ProcessMessages(1000);
+      for (int j = 0; j < HAPPY_TASK_COUNT; ++j) {
+        if (happy_[j].task_) {
+          happy_[j].task_->Wake();
+        }
+      }
+      LOG(LS_INFO) << "Polling tasks";
+      task_runner_.PollTasks();
+    }
+
+    // We see occasional test failures here due to the stuck tasks not having
+    // timed-out yet, which seems like it should be impossible. To help track
+    // this down we have added logging of the timing information, which we send
+    // directly to stdout so that we get it in opt builds too.
+    std::cout << "End time is " << GetCurrentTime() << std::endl;
+  }
+
+  void OnTimeoutStuck(const int id) {
+    LOG(LS_INFO) << "Timed out task " << id;
+
+    int i;
+    for (i = 0; i < STUCK_TASK_COUNT; ++i) {
+      if (stuck_[i].xlat_ == id) {
+        stuck_[i].timed_out_ = true;
+        stuck_[i].task_ = NULL;
+        break;
+      }
+    }
+
+    // getting a bad ID here is a failure, but let's continue
+    // running to see what else might go wrong
+    EXPECT_LT(i, STUCK_TASK_COUNT);
+  }
+
+  void OnTimeoutHappy(const int id) {
+    int i;
+    for (i = 0; i < HAPPY_TASK_COUNT; ++i) {
+      if (happy_[i].xlat_ == id) {
+        happy_[i].timed_out_ = true;
+        happy_[i].task_ = NULL;
+        break;
+      }
+    }
+
+    // getting a bad ID here is a failure, but let's continue
+    // running to see what else might go wrong
+    EXPECT_LT(i, HAPPY_TASK_COUNT);
+  }
+
+  void OnDoneHappy(const int id) {
+    int i;
+    for (i = 0; i < HAPPY_TASK_COUNT; ++i) {
+      if (happy_[i].xlat_ == id) {
+        happy_[i].task_ = NULL;
+        break;
+      }
+    }
+
+    // getting a bad ID here is a failure, but let's continue
+    // running to see what else might go wrong
+    EXPECT_LT(i, HAPPY_TASK_COUNT);
+  }
+
+  void check_passed() {
+    EXPECT_TRUE(task_runner_.AllChildrenDone());
+
+    // make sure none of our happy tasks timed out
+    for (int i = 0; i < HAPPY_TASK_COUNT; ++i) {
+      EXPECT_FALSE(happy_[i].timed_out_);
+    }
+
+    // make sure all of our stuck tasks timed out
+    for (int i = 0; i < STUCK_TASK_COUNT; ++i) {
+      EXPECT_TRUE(stuck_[i].timed_out_);
+      if (!stuck_[i].timed_out_) {
+        std::cout << "Stuck task #" << i << " timeout is at "
+            << stuck_[i].task_->timeout_time() << std::endl;        
+      }
+    }
+
+    std::cout.flush();
+  }
+
+ private:
+  struct TaskInfo {
+    IdTimeoutTask *task_;
+    bool timed_out_;
+    int xlat_;
+  };
+
+  MyTaskRunner task_runner_;
+  TaskInfo stuck_[STUCK_TASK_COUNT];
+  TaskInfo happy_[HAPPY_TASK_COUNT];
+};
+
+TEST(start_task_test, Timeout) {
+  TaskTest task_test;
+  task_test.Start();
+  task_test.check_passed();
+}
+
+// Test for aborting the task while it is running
+
+class AbortTask : public Task {
+ public:
+  explicit AbortTask(TaskParent *parent) : Task(parent) {
+    set_timeout_seconds(1);
+  }
+
+  virtual int ProcessStart() {
+    Abort();
+    return STATE_NEXT;
+  }
+ private:
+  DISALLOW_EVIL_CONSTRUCTORS(AbortTask);
+};
+
+class TaskAbortTest : public sigslot::has_slots<> {
+ public:
+  TaskAbortTest() {}
+
+  // no need to delete any tasks; the task runner owns them
+  ~TaskAbortTest() {}
+
+  void Start() {
+    Task *abort_task = new AbortTask(&task_runner_);
+    abort_task->SignalTimeout.connect(this, &TaskAbortTest::OnTimeout);
+    abort_task->Start();
+
+    // run the task
+    task_runner_.RunTasks();
+  }
+
+ private:
+  void OnTimeout() {
+    FAIL() << "Task timed out instead of aborting.";
+  }
+
+  MyTaskRunner task_runner_;
+  DISALLOW_EVIL_CONSTRUCTORS(TaskAbortTest);
+};
+
+TEST(start_task_test, Abort) {
+  TaskAbortTest abort_test;
+  abort_test.Start();
+}
+
+// Test for aborting a task to verify that it does the Wake operation
+// which gets it deleted.
+
+class SetBoolOnDeleteTask : public Task {
+ public:
+  SetBoolOnDeleteTask(TaskParent *parent, bool *set_when_deleted)
+    : Task(parent),
+      set_when_deleted_(set_when_deleted) {
+    EXPECT_TRUE(NULL != set_when_deleted);
+    EXPECT_FALSE(*set_when_deleted);
+  }
+
+  virtual ~SetBoolOnDeleteTask() {
+    *set_when_deleted_ = true;
+  }
+
+  virtual int ProcessStart() {
+    return STATE_BLOCKED;
+  }
+
+ private:
+  bool* set_when_deleted_;
+  DISALLOW_EVIL_CONSTRUCTORS(SetBoolOnDeleteTask);
+};
+
+class AbortShouldWakeTest : public sigslot::has_slots<> {
+ public:
+  AbortShouldWakeTest() {}
+
+  // no need to delete any tasks; the task runner owns them
+  ~AbortShouldWakeTest() {}
+
+  void Start() {
+    bool task_deleted = false;
+    Task *task_to_abort = new SetBoolOnDeleteTask(&task_runner_, &task_deleted);
+    task_to_abort->Start();
+
+    // Task::Abort() should call TaskRunner::WakeTasks(). WakeTasks calls
+    // TaskRunner::RunTasks() immediately which should delete the task.
+    task_to_abort->Abort();
+    EXPECT_TRUE(task_deleted);
+
+    if (!task_deleted) {
+      // avoid a crash (due to referencing a local variable)
+      // if the test fails.
+      task_runner_.RunTasks();
+    }
+  }
+
+ private:
+  void OnTimeout() {
+    FAIL() << "Task timed out instead of aborting.";
+  }
+
+  MyTaskRunner task_runner_;
+  DISALLOW_EVIL_CONSTRUCTORS(AbortShouldWakeTest);
+};
+
+TEST(start_task_test, AbortShouldWake) {
+  AbortShouldWakeTest abort_should_wake_test;
+  abort_should_wake_test.Start();
+}
+
+// Validate that TaskRunner's OnTimeoutChange gets called appropriately
+//  * When a task calls UpdateTaskTimeout
+//  * When the next timeout task time, times out
+class TimeoutChangeTest : public sigslot::has_slots<> {
+ public:
+  TimeoutChangeTest()
+    : task_count_(ARRAY_SIZE(stuck_tasks_)) {}
+
+  // no need to delete any tasks; the task runner owns them
+  ~TimeoutChangeTest() {}
+
+  void Start() {
+    for (int i = 0; i < task_count_; ++i) {
+      stuck_tasks_[i] = new StuckTask(&task_runner_);
+      stuck_tasks_[i]->set_timeout_seconds(i + 2);
+      stuck_tasks_[i]->SignalTimeoutId.connect(this,
+                                               &TimeoutChangeTest::OnTimeoutId);
+    }
+
+    for (int i = task_count_ - 1; i >= 0; --i) {
+      stuck_tasks_[i]->Start();
+    }
+    task_runner_.clear_timeout_change();
+
+    // At this point, our timeouts are set as follows
+    // task[0] is 2 seconds, task[1] at 3 seconds, etc.
+
+    stuck_tasks_[0]->set_timeout_seconds(2);
+    // Now, task[0] is 2 seconds, task[1] at 3 seconds...
+    // so timeout change shouldn't be called.
+    EXPECT_FALSE(task_runner_.timeout_change());
+    task_runner_.clear_timeout_change();
+
+    stuck_tasks_[0]->set_timeout_seconds(1);
+    // task[0] is 1 seconds, task[1] at 3 seconds...
+    // The smallest timeout got smaller so timeout change be called.
+    EXPECT_TRUE(task_runner_.timeout_change());
+    task_runner_.clear_timeout_change();
+
+    stuck_tasks_[1]->set_timeout_seconds(2);
+    // task[0] is 1 seconds, task[1] at 2 seconds...
+    // The smallest timeout is still 1 second so no timeout change.
+    EXPECT_FALSE(task_runner_.timeout_change());
+    task_runner_.clear_timeout_change();
+
+    while (task_count_ > 0) {
+      int previous_count = task_count_;
+      task_runner_.PollTasks();
+      if (previous_count != task_count_) {
+        // We only get here when a task times out.  When that
+        // happens, the timeout change should get called because
+        // the smallest timeout is now in the past.
+        EXPECT_TRUE(task_runner_.timeout_change());
+        task_runner_.clear_timeout_change();
+      }
+      Thread::Current()->socketserver()->Wait(500, false);
+    }
+  }
+
+ private:
+  void OnTimeoutId(const int id) {
+    for (int i = 0; i < ARRAY_SIZE(stuck_tasks_); ++i) {
+      if (stuck_tasks_[i] && stuck_tasks_[i]->unique_id() == id) {
+        task_count_--;
+        stuck_tasks_[i] = NULL;
+        break;
+      }
+    }
+  }
+
+  MyTaskRunner task_runner_;
+  StuckTask* (stuck_tasks_[3]);
+  int task_count_;
+  DISALLOW_EVIL_CONSTRUCTORS(TimeoutChangeTest);
+};
+
+TEST(start_task_test, TimeoutChange) {
+  TimeoutChangeTest timeout_change_test;
+  timeout_change_test.Start();
+}
+
+class DeleteTestTaskRunner : public TaskRunner {
+ public:
+  DeleteTestTaskRunner() {
+  }
+  virtual void WakeTasks() { }
+  virtual int64 CurrentTime() {
+    return GetCurrentTime();
+  }
+ private:
+  DISALLOW_EVIL_CONSTRUCTORS(DeleteTestTaskRunner);
+};
+
+TEST(unstarted_task_test, DeleteTask) {
+  // This test ensures that we don't
+  // crash if a task is deleted without running it.
+  DeleteTestTaskRunner task_runner;
+  HappyTask* happy_task = new HappyTask(&task_runner);
+  happy_task->Start();
+
+  // try deleting the task directly
+  HappyTask* child_happy_task = new HappyTask(happy_task);
+  delete child_happy_task;
+
+  // run the unblocked tasks
+  task_runner.RunTasks();
+}
+
+TEST(unstarted_task_test, DoNotDeleteTask1) {
+  // This test ensures that we don't
+  // crash if a task runner is deleted without
+  // running a certain task.
+  DeleteTestTaskRunner task_runner;
+  HappyTask* happy_task = new HappyTask(&task_runner);
+  happy_task->Start();
+
+  HappyTask* child_happy_task = new HappyTask(happy_task);
+  child_happy_task->Start();
+
+  // Never run the tasks
+}
+
+TEST(unstarted_task_test, DoNotDeleteTask2) {
+  // This test ensures that we don't
+  // crash if a taskrunner is delete with a
+  // task that has never been started.
+  DeleteTestTaskRunner task_runner;
+  HappyTask* happy_task = new HappyTask(&task_runner);
+  happy_task->Start();
+
+  // Do not start the task.
+  // Note: this leaks memory, so don't do this.
+  // Instead, always run your tasks or delete them.
+  new HappyTask(happy_task);
+
+  // run the unblocked tasks
+  task_runner.RunTasks();
+}
+
+}  // namespace talk_base
diff --git a/talk/base/testechoserver.h b/talk/base/testechoserver.h
new file mode 100644
index 0000000..0b645d1
--- /dev/null
+++ b/talk/base/testechoserver.h
@@ -0,0 +1,63 @@
+// Copyright 2008 Google Inc. All Rights Reserved.
+
+
+#ifndef TALK_BASE_TESTECHOSERVER_H_
+#define TALK_BASE_TESTECHOSERVER_H_
+
+#include <list>
+#include "talk/base/asynctcpsocket.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+// A test echo server, echoes back any packets sent to it.
+// Useful for unit tests.
+class TestEchoServer : public sigslot::has_slots<> {
+ public:
+  TestEchoServer(Thread* thread, const SocketAddress& addr)
+      : server_socket_(thread->socketserver()->CreateAsyncSocket(SOCK_STREAM)) {
+    server_socket_->Bind(addr);
+    server_socket_->Listen(5);
+    server_socket_->SignalReadEvent.connect(this, &TestEchoServer::OnAccept);
+  }
+  ~TestEchoServer() {
+    for (ClientList::iterator it = client_sockets_.begin();
+         it != client_sockets_.end(); ++it) {
+      delete *it;
+    }
+  }
+
+  SocketAddress address() const { return server_socket_->GetLocalAddress(); }
+
+ private:
+  void OnAccept(AsyncSocket* socket) {
+    AsyncSocket* raw_socket = socket->Accept(NULL);
+    if (raw_socket) {
+      AsyncTCPSocket* packet_socket = new AsyncTCPSocket(raw_socket, false);
+      packet_socket->SignalReadPacket.connect(this, &TestEchoServer::OnPacket);
+      packet_socket->SignalClose.connect(this, &TestEchoServer::OnClose);
+      client_sockets_.push_back(packet_socket);
+    }
+  }
+  void OnPacket(AsyncPacketSocket* socket, const char* buf, size_t size,
+                const SocketAddress& remote_addr) {
+    socket->Send(buf, size);
+  }
+  void OnClose(AsyncPacketSocket* socket, int err) {
+    ClientList::iterator it =
+        std::find(client_sockets_.begin(), client_sockets_.end(), socket);
+    client_sockets_.erase(it);
+    Thread::Current()->Dispose(socket);
+  }
+
+  typedef std::list<AsyncTCPSocket*> ClientList;
+  scoped_ptr<AsyncSocket> server_socket_;
+  ClientList client_sockets_;
+  DISALLOW_EVIL_CONSTRUCTORS(TestEchoServer);
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_TESTECHOSERVER_H_
diff --git a/talk/base/testutils.h b/talk/base/testutils.h
new file mode 100644
index 0000000..4dc648e
--- /dev/null
+++ b/talk/base/testutils.h
@@ -0,0 +1,568 @@
+/*
+ * libjingle
+ * Copyright 2004--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_TESTUTILS_H__
+#define TALK_BASE_TESTUTILS_H__
+
+// Utilities for testing talk_base infrastructure in unittests
+
+#include <map>
+#include <vector>
+#include "talk/base/asyncsocket.h"
+#include "talk/base/common.h"
+#include "talk/base/gunit.h"
+#include "talk/base/stream.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/thread.h"
+
+namespace testing {
+
+using namespace talk_base;
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamSink - Monitor asynchronously signalled events from StreamInterface
+// or AsyncSocket (which should probably be a StreamInterface.
+///////////////////////////////////////////////////////////////////////////////
+
+// Note: Any event that is an error is treaded as SSE_ERROR instead of that
+// event.
+
+enum StreamSinkEvent {
+  SSE_OPEN  = SE_OPEN,
+  SSE_READ  = SE_READ,
+  SSE_WRITE = SE_WRITE,
+  SSE_CLOSE = SE_CLOSE,
+  SSE_ERROR = 16
+};
+
+class StreamSink : public sigslot::has_slots<> {
+ public:
+  void Monitor(StreamInterface* stream) {
+   stream->SignalEvent.connect(this, &StreamSink::OnEvent);
+   events_.erase(stream);
+  }
+  void Unmonitor(StreamInterface* stream) {
+   stream->SignalEvent.disconnect(this);
+   // In case you forgot to unmonitor a previous object with this address
+   events_.erase(stream);
+  }
+  bool Check(StreamInterface* stream, StreamSinkEvent event, bool reset = true) {
+    return DoCheck(stream, event, reset);
+  }
+  int Events(StreamInterface* stream, bool reset = true) {
+    return DoEvents(stream, reset);
+  }
+
+  void Monitor(AsyncSocket* socket) {
+   socket->SignalConnectEvent.connect(this, &StreamSink::OnConnectEvent);
+   socket->SignalReadEvent.connect(this, &StreamSink::OnReadEvent);
+   socket->SignalWriteEvent.connect(this, &StreamSink::OnWriteEvent);
+   socket->SignalCloseEvent.connect(this, &StreamSink::OnCloseEvent);
+   // In case you forgot to unmonitor a previous object with this address
+   events_.erase(socket);
+  }
+  void Unmonitor(AsyncSocket* socket) {
+   socket->SignalConnectEvent.disconnect(this);
+   socket->SignalReadEvent.disconnect(this);
+   socket->SignalWriteEvent.disconnect(this);
+   socket->SignalCloseEvent.disconnect(this);
+   events_.erase(socket);
+  }
+  bool Check(AsyncSocket* socket, StreamSinkEvent event, bool reset = true) {
+    return DoCheck(socket, event, reset);
+  }
+  int Events(AsyncSocket* socket, bool reset = true) {
+    return DoEvents(socket, reset);
+  }
+
+ private:
+  typedef std::map<void*,int> EventMap;
+
+  void OnEvent(StreamInterface* stream, int events, int error) {
+    if (error) {
+      events = SSE_ERROR;
+    }
+    AddEvents(stream, events);
+  }
+  void OnConnectEvent(AsyncSocket* socket) {
+    AddEvents(socket, SSE_OPEN);
+  }
+  void OnReadEvent(AsyncSocket* socket) {
+    AddEvents(socket, SSE_READ);
+  }
+  void OnWriteEvent(AsyncSocket* socket) {
+    AddEvents(socket, SSE_WRITE);
+  }
+  void OnCloseEvent(AsyncSocket* socket, int error) {
+    AddEvents(socket, (0 == error) ? SSE_CLOSE : SSE_ERROR);
+  }
+
+  void AddEvents(void* obj, int events) {
+    EventMap::iterator it = events_.find(obj);
+    if (events_.end() == it) {
+      events_.insert(EventMap::value_type(obj, events));
+    } else {
+      it->second |= events;
+    }
+  }
+  bool DoCheck(void* obj, StreamSinkEvent event, bool reset) {
+    EventMap::iterator it = events_.find(obj);
+    if ((events_.end() == it) || (0 == (it->second & event))) {
+      return false;
+    }
+    if (reset) {
+      it->second &= ~event;
+    }
+    return true;
+  }
+  int DoEvents(void* obj, bool reset) {
+    EventMap::iterator it = events_.find(obj);
+    if (events_.end() == it)
+      return 0;
+    int events = it->second;
+    if (reset) {
+      it->second = 0;
+    }
+    return events;
+  }
+
+  EventMap events_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamSource - Implements stream interface and simulates asynchronous
+// events on the stream, without a network.  Also buffers written data.
+///////////////////////////////////////////////////////////////////////////////
+
+class StreamSource : public StreamInterface {
+public:
+  StreamSource() {
+    Clear();
+  }
+
+  void Clear() {
+    readable_data_.clear();
+    written_data_.clear();
+    state_ = SS_CLOSED;
+    read_block_ = 0;
+    write_block_ = SIZE_UNKNOWN;
+  }
+  void QueueString(const char* data) {
+    QueueData(data, strlen(data));
+  }
+  void QueueStringF(const char* format, ...) {
+    va_list args;
+    va_start(args, format);
+    char buffer[1024];
+    size_t len = vsprintfn(buffer, sizeof(buffer), format, args);
+    ASSERT(len < sizeof(buffer) - 1);
+    va_end(args);
+    QueueData(buffer, len);
+  }
+  void QueueData(const char* data, size_t len) {
+    readable_data_.insert(readable_data_.end(), data, data + len);
+    if ((SS_OPEN == state_) && (readable_data_.size() == len)) {
+      SignalEvent(this, SE_READ, 0);
+    }
+  }
+  std::string ReadData() {
+    std::string data;
+    // avoid accessing written_data_[0] if it is undefined
+    if (written_data_.size() > 0) {
+      data.insert(0, &written_data_[0], written_data_.size());
+    }
+    written_data_.clear();
+    return data;
+  }
+  void SetState(StreamState state) {
+    int events = 0;
+    if ((SS_OPENING == state_) && (SS_OPEN == state)) {
+      events |= SE_OPEN;
+      if (!readable_data_.empty()) {
+        events |= SE_READ;
+      }
+    } else if ((SS_CLOSED != state_) && (SS_CLOSED == state)) {
+      events |= SE_CLOSE;
+    }
+    state_ = state;
+    if (events) {
+      SignalEvent(this, events, 0);
+    }
+  }
+  // Will cause Read to block when there are pos bytes in the read queue.
+  void SetReadBlock(size_t pos) { read_block_ = pos; }
+  // Will cause Write to block when there are pos bytes in the write queue.
+  void SetWriteBlock(size_t pos) { write_block_ = pos; }
+
+  virtual StreamState GetState() const { return state_; }
+  virtual StreamResult Read(void* buffer, size_t buffer_len,
+                            size_t* read, int* error) {
+    if (SS_CLOSED == state_) {
+      if (error) *error = -1;
+      return SR_ERROR;
+    }
+    if ((SS_OPENING == state_) || (readable_data_.size() <= read_block_)) {
+      return SR_BLOCK;
+    }
+    size_t count = _min(buffer_len, readable_data_.size() - read_block_);
+    memcpy(buffer, &readable_data_[0], count);
+    size_t new_size = readable_data_.size() - count;
+    // Avoid undefined access beyond the last element of the vector.
+    // This only happens when new_size is 0.
+    if (count < readable_data_.size()) {
+      memmove(&readable_data_[0], &readable_data_[count], new_size);
+    }
+    readable_data_.resize(new_size);
+    if (read) *read = count;
+    return SR_SUCCESS;
+  }
+  virtual StreamResult Write(const void* data, size_t data_len,
+                             size_t* written, int* error) {
+    if (SS_CLOSED == state_) {
+      if (error) *error = -1;
+      return SR_ERROR;
+    }
+    if (SS_OPENING == state_) {
+      return SR_BLOCK;
+    }
+    if (SIZE_UNKNOWN != write_block_) {
+      if (written_data_.size() >= write_block_) {
+        return SR_BLOCK;
+      }
+      if (data_len > (write_block_ - written_data_.size())) {
+        data_len = write_block_ - written_data_.size();
+      }
+    }
+    if (written) *written = data_len;
+    const char* cdata = static_cast<const char*>(data);
+    written_data_.insert(written_data_.end(), cdata, cdata + data_len);
+    return SR_SUCCESS;
+  }
+  virtual void Close() { state_ = SS_CLOSED; }
+
+private:
+  typedef std::vector<char> Buffer;
+  Buffer readable_data_, written_data_;
+  StreamState state_;
+  size_t read_block_, write_block_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// SocketTestClient
+// Creates a simulated client for testing.  Works on real and virtual networks.
+///////////////////////////////////////////////////////////////////////////////
+
+class SocketTestClient : public sigslot::has_slots<> {
+public:
+  SocketTestClient() {
+    Init(NULL);
+  }
+  SocketTestClient(AsyncSocket* socket) {
+    Init(socket);
+  }
+  SocketTestClient(const SocketAddress& address) {
+    Init(NULL);
+    socket_->Connect(address);
+  }
+
+  AsyncSocket* socket() { return socket_.get(); }
+
+  void QueueString(const char* data) {
+    QueueData(data, strlen(data));
+  }
+  void QueueStringF(const char* format, ...) {
+    va_list args;
+    va_start(args, format);
+    char buffer[1024];
+    size_t len = vsprintfn(buffer, sizeof(buffer), format, args);
+    ASSERT(len < sizeof(buffer) - 1);
+    va_end(args);
+    QueueData(buffer, len);
+  }
+  void QueueData(const char* data, size_t len) {
+    send_buffer_.insert(send_buffer_.end(), data, data + len);
+    if (Socket::CS_CONNECTED == socket_->GetState()) {
+      Flush();
+    }
+  }
+  std::string ReadData() {
+    std::string data(&recv_buffer_[0], recv_buffer_.size());
+    recv_buffer_.clear();
+    return data;
+  }
+
+  bool IsConnected() const {
+    return (Socket::CS_CONNECTED == socket_->GetState());
+  }
+  bool IsClosed() const {
+    return (Socket::CS_CLOSED == socket_->GetState());
+  }
+
+private:
+  typedef std::vector<char> Buffer;
+
+  void Init(AsyncSocket* socket) {
+    if (!socket) {
+      socket = Thread::Current()->socketserver()
+                                ->CreateAsyncSocket(SOCK_STREAM);
+    }
+    socket_.reset(socket);
+    socket_->SignalConnectEvent.connect(this,
+      &SocketTestClient::OnConnectEvent);
+    socket_->SignalReadEvent.connect(this, &SocketTestClient::OnReadEvent);
+    socket_->SignalWriteEvent.connect(this, &SocketTestClient::OnWriteEvent);
+    socket_->SignalCloseEvent.connect(this, &SocketTestClient::OnCloseEvent);
+  }
+
+  void Flush() {
+    size_t sent = 0;
+    while (sent < send_buffer_.size()) {
+      int result = socket_->Send(&send_buffer_[sent],
+                                 send_buffer_.size() - sent);
+      if (result > 0) {
+        sent += result;
+      } else {
+        break;
+      }
+    }
+    size_t new_size = send_buffer_.size() - sent;
+    memmove(&send_buffer_[0], &send_buffer_[sent], new_size);
+    send_buffer_.resize(new_size);
+  }
+
+  void OnConnectEvent(AsyncSocket* socket) {
+    if (!send_buffer_.empty()) {
+      Flush();
+    }
+  }
+  void OnReadEvent(AsyncSocket* socket) {
+    char data[64 * 1024];
+    int result = socket_->Recv(data, ARRAY_SIZE(data));
+    if (result > 0) {
+      recv_buffer_.insert(recv_buffer_.end(), data, data + result);
+    }
+  }
+  void OnWriteEvent(AsyncSocket* socket) {
+    if (!send_buffer_.empty()) {
+      Flush();
+    }
+  }
+  void OnCloseEvent(AsyncSocket* socket, int error) {
+  }
+
+  scoped_ptr<AsyncSocket> socket_;
+  Buffer send_buffer_, recv_buffer_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// SocketTestServer
+// Creates a simulated server for testing.  Works on real and virtual networks.
+///////////////////////////////////////////////////////////////////////////////
+
+class SocketTestServer : public sigslot::has_slots<> {
+ public:
+  SocketTestServer(const SocketAddress& address)
+  : socket_(Thread::Current()->socketserver()->CreateAsyncSocket(SOCK_STREAM))
+  {
+    socket_->SignalReadEvent.connect(this, &SocketTestServer::OnReadEvent);
+    socket_->Bind(address);
+    socket_->Listen(5);
+  }
+  virtual ~SocketTestServer() {
+    clear();
+  }
+
+  size_t size() const { return clients_.size(); }
+  SocketTestClient* client(size_t index) const { return clients_[index]; }
+  SocketTestClient* operator[](size_t index) const { return client(index); }
+
+  void clear() {
+    for (size_t i=0; i<clients_.size(); ++i) {
+      delete clients_[i];
+    }
+    clients_.clear();
+  }
+
+ private:
+  void OnReadEvent(AsyncSocket* socket) {
+    AsyncSocket* accepted =
+      static_cast<AsyncSocket*>(socket_->Accept(NULL));
+    if (!accepted)
+      return;
+    clients_.push_back(new SocketTestClient(accepted));
+  }
+
+  scoped_ptr<AsyncSocket> socket_;
+  std::vector<SocketTestClient*> clients_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Generic Utilities
+///////////////////////////////////////////////////////////////////////////////
+
+inline bool ReadFile(const char* filename, std::string* contents) {
+  FILE* fp = fopen(filename, "rb");
+  if (!fp)
+    return false;
+  char buffer[1024*64];
+  size_t read;
+  contents->clear();
+  while ((read = fread(buffer, 1, sizeof(buffer), fp))) {
+    contents->append(buffer, read);
+  }
+  bool success = (0 != feof(fp));
+  fclose(fp);
+  return success;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Unittest predicates which are similar to STREQ, but for raw memory
+///////////////////////////////////////////////////////////////////////////////
+
+inline AssertionResult CmpHelperMemEq(const char* expected_expression,
+                                      const char* expected_length_expression,
+                                      const char* actual_expression,
+                                      const char* actual_length_expression,
+                                      const void* expected,
+                                      size_t expected_length,
+                                      const void* actual,
+                                      size_t actual_length)
+{
+  if ((expected_length == actual_length)
+      && (0 == memcmp(expected, actual, expected_length))) {
+    return AssertionSuccess();
+  }
+
+  Message msg;
+  msg << "Value of: " << actual_expression
+      << " [" << actual_length_expression << "]";
+  if (true) {  //!actual_value.Equals(actual_expression)) {
+    size_t buffer_size = actual_length * 2 + 1;
+    char* buffer = STACK_ARRAY(char, buffer_size);
+    hex_encode(buffer, buffer_size,
+               reinterpret_cast<const char*>(actual), actual_length);
+    msg << "\n  Actual: " << buffer << " [" << actual_length << "]";
+  }
+
+  msg << "\nExpected: " << expected_expression
+      << " [" << expected_length_expression << "]";
+  if (true) {  //!expected_value.Equals(expected_expression)) {
+    size_t buffer_size = expected_length * 2 + 1;
+    char* buffer = STACK_ARRAY(char, buffer_size);
+    hex_encode(buffer, buffer_size,
+               reinterpret_cast<const char*>(expected), expected_length);
+    msg << "\nWhich is: " << buffer << " [" << expected_length << "]";
+  }
+
+  return AssertionFailure(msg);
+}
+
+inline AssertionResult CmpHelperFileEq(const char* expected_expression,
+                                       const char* expected_length_expression,
+                                       const char* actual_filename,
+                                       const void* expected,
+                                       size_t expected_length,
+                                       const char* filename)
+{
+  std::string contents;
+  if (!ReadFile(filename, &contents)) {
+    Message msg;
+    msg << "File '" << filename << "' could not be read.";
+    return AssertionFailure(msg);
+  }
+  return CmpHelperMemEq(expected_expression, expected_length_expression,
+                        actual_filename, "",
+                        expected, expected_length,
+                        contents.c_str(), contents.size());
+}
+
+#define EXPECT_MEMEQ(expected, expected_length, actual, actual_length) \
+  EXPECT_PRED_FORMAT4(::testing::CmpHelperMemEq, expected, expected_length, \
+                      actual, actual_length)
+
+#define ASSERT_MEMEQ(expected, expected_length, actual, actual_length) \
+  ASSERT_PRED_FORMAT4(::testing::CmpHelperMemEq, expected, expected_length, \
+                      actual, actual_length)
+
+#define EXPECT_FILEEQ(expected, expected_length, filename) \
+  EXPECT_PRED_FORMAT3(::testing::CmpHelperFileEq, expected, expected_length, \
+                      filename)
+
+#define ASSERT_FILEEQ(expected, expected_length, filename) \
+  ASSERT_PRED_FORMAT3(::testing::CmpHelperFileEq, expected, expected_length, \
+                      filename)
+
+///////////////////////////////////////////////////////////////////////////////
+// Helpers for initializing constant memory with integers in a particular byte
+// order
+///////////////////////////////////////////////////////////////////////////////
+
+#define BYTE_CAST(x) static_cast<uint8>((x) & 0xFF)
+
+// Declare a N-bit integer as a little-endian sequence of bytes
+#define LE16(x) BYTE_CAST(((uint16)x) >>  0), BYTE_CAST(((uint16)x) >>  8)
+
+#define LE32(x) BYTE_CAST(((uint32)x) >>  0), BYTE_CAST(((uint32)x) >>  8), \
+                BYTE_CAST(((uint32)x) >> 16), BYTE_CAST(((uint32)x) >> 24)
+
+#define LE64(x) BYTE_CAST(((uint64)x) >>  0), BYTE_CAST(((uint64)x) >>  8), \
+                BYTE_CAST(((uint64)x) >> 16), BYTE_CAST(((uint64)x) >> 24), \
+                BYTE_CAST(((uint64)x) >> 32), BYTE_CAST(((uint64)x) >> 40), \
+                BYTE_CAST(((uint64)x) >> 48), BYTE_CAST(((uint64)x) >> 56)
+
+// Declare a N-bit integer as a big-endian (Internet) sequence of bytes
+#define BE16(x) BYTE_CAST(((uint16)x) >>  8), BYTE_CAST(((uint16)x) >>  0)
+
+#define BE32(x) BYTE_CAST(((uint32)x) >> 24), BYTE_CAST(((uint32)x) >> 16), \
+                BYTE_CAST(((uint32)x) >>  8), BYTE_CAST(((uint32)x) >>  0)
+
+#define BE64(x) BYTE_CAST(((uint64)x) >> 56), BYTE_CAST(((uint64)x) >> 48), \
+                BYTE_CAST(((uint64)x) >> 40), BYTE_CAST(((uint64)x) >> 32), \
+                BYTE_CAST(((uint64)x) >> 24), BYTE_CAST(((uint64)x) >> 16), \
+                BYTE_CAST(((uint64)x) >>  8), BYTE_CAST(((uint64)x) >>  0)
+
+// Declare a N-bit integer as a this-endian (local machine) sequence of bytes
+#ifndef BIG_ENDIAN
+#define BIG_ENDIAN 1
+#endif  // BIG_ENDIAN
+
+#if BIG_ENDIAN
+#define TE16 BE16
+#define TE32 BE32
+#define TE64 BE64
+#else  // !BIG_ENDIAN
+#define TE16 LE16
+#define TE32 LE32
+#define TE64 LE64
+#endif  // !BIG_ENDIAN
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace testing
+
+#endif  // TALK_BASE_TESTUTILS_H__
diff --git a/talk/base/thread_unittest.cc b/talk/base/thread_unittest.cc
new file mode 100644
index 0000000..a70b52d
--- /dev/null
+++ b/talk/base/thread_unittest.cc
@@ -0,0 +1,260 @@
+/*
+ * libjingle
+ * Copyright 2004--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/asyncudpsocket.h"
+#include "talk/base/gunit.h"
+#include "talk/base/host.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/thread.h"
+
+#ifdef WIN32
+#include <comdef.h>  // NOLINT
+#endif
+
+using namespace talk_base;
+
+const int MAX = 65536;
+
+// Generates a sequence of numbers (collaboratively).
+class TestGenerator {
+ public:
+  TestGenerator() : last(0), count(0) {}
+
+  int Next(int prev) {
+    int result = prev + last;
+    last = result;
+    count += 1;
+    return result;
+  }
+
+  int last;
+  int count;
+};
+
+struct TestMessage : public MessageData {
+  explicit TestMessage(int v) : value(v) {}
+  virtual ~TestMessage() {}
+
+  int value;
+};
+
+// Receives on a socket and sends by posting messages.
+class SocketClient : public TestGenerator, public sigslot::has_slots<> {
+ public:
+  SocketClient(AsyncSocket* socket, const SocketAddress& addr,
+               Thread* post_thread, MessageHandler* phandler)
+      : socket_(AsyncUDPSocket::Create(socket, addr)),
+        post_thread_(post_thread),
+        post_handler_(phandler) {
+    socket_->SignalReadPacket.connect(this, &SocketClient::OnPacket);
+  }
+
+  ~SocketClient() {
+    delete socket_;
+  }
+
+  SocketAddress address() const { return socket_->GetLocalAddress(); }
+
+  void OnPacket(AsyncPacketSocket* socket, const char* buf, size_t size,
+                const SocketAddress& remote_addr) {
+    EXPECT_EQ(size, sizeof(uint32));
+    uint32 prev = reinterpret_cast<const uint32*>(buf)[0];
+    uint32 result = Next(prev);
+
+    //socket_->set_readable(last < MAX);
+    post_thread_->PostDelayed(200, post_handler_, 0, new TestMessage(result));
+  }
+
+ private:
+  AsyncUDPSocket* socket_;
+  Thread* post_thread_;
+  MessageHandler* post_handler_;
+};
+
+// Receives messages and sends on a socket.
+class MessageClient : public MessageHandler, public TestGenerator {
+ public:
+  MessageClient(Thread* pth, Socket* socket)
+      : thread_(pth), socket_(socket) {
+  }
+
+  virtual ~MessageClient() {
+    delete socket_;
+  }
+
+  virtual void OnMessage(Message *pmsg) {
+    TestMessage* msg = static_cast<TestMessage*>(pmsg->pdata);
+    int result = Next(msg->value);
+    EXPECT_GE(socket_->Send(&result, sizeof(result)), 0);
+    delete msg;
+  }
+
+ private:
+  Thread* thread_;
+  Socket* socket_;
+};
+
+class CustomThread : public talk_base::Thread {
+ public:
+  CustomThread() {}
+  virtual ~CustomThread() {}
+  bool Start() { return false; }
+
+  bool WrapCurrent() { return talk_base::Thread::WrapCurrent(); }
+  void UnwrapCurrent() { talk_base::Thread::UnwrapCurrent(); }
+};
+
+
+TEST(ThreadTest, Main) {
+  const SocketAddress addr("127.0.0.1", 0);
+
+  // Create the messaging client on its own thread.
+  Thread th1;
+  Socket* socket = th1.socketserver()->CreateSocket(SOCK_DGRAM);
+  MessageClient msg_client(&th1, socket);
+
+  // Create the socket client on its own thread.
+  Thread th2;
+  AsyncSocket* asocket = th2.socketserver()->CreateAsyncSocket(SOCK_DGRAM);
+  SocketClient sock_client(asocket, addr, &th1, &msg_client);
+
+  socket->Connect(sock_client.address());
+
+  th1.Start();
+  th2.Start();
+
+  // Get the messages started.
+  th1.PostDelayed(100, &msg_client, 0, new TestMessage(1));
+
+  // Give the clients a little while to run.
+  // Messages will be processed at 100, 300, 500, 700, 900.
+  Thread* th_main = Thread::Current();
+  th_main->ProcessMessages(1000);
+
+  // Stop the sending client. Give the receiver a bit longer to run, in case
+  // it is running on a machine that is under load (e.g. the build machine).
+  th1.Stop();
+  th_main->ProcessMessages(200);
+  th2.Stop();
+
+  // Make sure the results were correct
+  EXPECT_EQ(5, msg_client.count);
+  EXPECT_EQ(34, msg_client.last);
+  EXPECT_EQ(5, sock_client.count);
+  EXPECT_EQ(55, sock_client.last);
+}
+
+// Test that setting thread names doesn't cause a malfunction.
+// There's no easy way to verify the name was set properly at this time.
+TEST(ThreadTest, Names) {
+  // Default name
+  Thread *thread;
+  thread = new Thread();
+  EXPECT_TRUE(thread->Start());
+  thread->Stop();
+  delete thread;
+  thread = new Thread();
+  // Name with no object parameter
+  EXPECT_TRUE(thread->SetName("No object", NULL));
+  EXPECT_TRUE(thread->Start());
+  thread->Stop();
+  delete thread;
+  // Really long name
+  thread = new Thread();
+  EXPECT_TRUE(thread->SetName("Abcdefghijklmnopqrstuvwxyz1234567890", this));
+  EXPECT_TRUE(thread->Start());
+  thread->Stop();
+  delete thread;
+}
+
+// Test that setting thread priorities doesn't cause a malfunction.
+// There's no easy way to verify the priority was set properly at this time.
+TEST(ThreadTest, Priorities) {
+  Thread *thread;
+  thread = new Thread();
+  EXPECT_TRUE(thread->SetPriority(PRIORITY_HIGH));
+  EXPECT_TRUE(thread->Start());
+  thread->Stop();
+  delete thread;
+  thread = new Thread();
+  EXPECT_TRUE(thread->SetPriority(PRIORITY_ABOVE_NORMAL));
+  EXPECT_TRUE(thread->Start());
+  thread->Stop();
+  delete thread;
+
+  thread = new Thread();
+  EXPECT_TRUE(thread->Start());
+#ifdef WIN32
+  EXPECT_TRUE(thread->SetPriority(PRIORITY_ABOVE_NORMAL));
+#else
+  EXPECT_FALSE(thread->SetPriority(PRIORITY_ABOVE_NORMAL));
+#endif
+  thread->Stop();
+  delete thread;
+
+}
+
+TEST(ThreadTest, Wrap) {
+  Thread* current_thread = ThreadManager::CurrentThread();
+  current_thread->UnwrapCurrent();
+  CustomThread* cthread = new CustomThread();
+  EXPECT_TRUE(cthread->WrapCurrent());
+  EXPECT_TRUE(cthread->started());
+  EXPECT_FALSE(cthread->IsOwned());
+  cthread->UnwrapCurrent();
+  EXPECT_FALSE(cthread->started());
+  delete cthread;
+  current_thread->WrapCurrent();
+}
+
+
+#ifdef WIN32
+class ComThreadTest : public testing::Test, public MessageHandler {
+ public:
+  ComThreadTest() : done_(false) {}
+ protected:
+  virtual void OnMessage(Message* message) {
+    HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
+    // S_FALSE means the thread was already inited for a multithread apartment.
+    EXPECT_EQ(S_FALSE, hr);
+    if (SUCCEEDED(hr)) {
+      CoUninitialize();
+    }
+    done_ = true;
+  }
+  bool done_;
+};
+
+TEST_F(ComThreadTest, ComInited) {
+  Thread* thread = new ComThread();
+  EXPECT_TRUE(thread->Start());
+  thread->Post(this, 0);
+  EXPECT_TRUE_WAIT(done_, 1000);
+  delete thread;
+}
+#endif
diff --git a/talk/base/time_unittest.cc b/talk/base/time_unittest.cc
new file mode 100644
index 0000000..52e5111
--- /dev/null
+++ b/talk/base/time_unittest.cc
@@ -0,0 +1,136 @@
+/*
+ * libjingle
+ * Copyright 2004--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/common.h"
+#include "talk/base/gunit.h"
+#include "talk/base/thread.h"
+#include "talk/base/time.h"
+
+namespace talk_base {
+
+TEST(TimeTest, Comparison) {
+  // Obtain two different times, in known order
+  TimeStamp ts_earlier = Time();
+  Thread::SleepMs(100);
+  TimeStamp ts_now = Time();
+  EXPECT_NE(ts_earlier, ts_now);
+
+  // Common comparisons
+  EXPECT_TRUE( TimeIsLaterOrEqual(ts_earlier, ts_now));
+  EXPECT_TRUE( TimeIsLater(       ts_earlier, ts_now));
+  EXPECT_FALSE(TimeIsLaterOrEqual(ts_now,     ts_earlier));
+  EXPECT_FALSE(TimeIsLater(       ts_now,     ts_earlier));
+
+  // Edge cases
+  EXPECT_TRUE( TimeIsLaterOrEqual(ts_earlier, ts_earlier));
+  EXPECT_FALSE(TimeIsLater(       ts_earlier, ts_earlier));
+
+  // Obtain a third time
+  TimeStamp ts_later = TimeAfter(100);
+  EXPECT_NE(ts_now, ts_later);
+  EXPECT_TRUE( TimeIsLater(ts_now,     ts_later));
+  EXPECT_TRUE( TimeIsLater(ts_earlier, ts_later));
+
+  // Common comparisons
+  EXPECT_TRUE( TimeIsBetween(ts_earlier, ts_now,     ts_later));
+  EXPECT_FALSE(TimeIsBetween(ts_earlier, ts_later,   ts_now));
+  EXPECT_FALSE(TimeIsBetween(ts_now,     ts_earlier, ts_later));
+  EXPECT_TRUE( TimeIsBetween(ts_now,     ts_later,   ts_earlier));
+  EXPECT_TRUE( TimeIsBetween(ts_later,   ts_earlier, ts_now));
+  EXPECT_FALSE(TimeIsBetween(ts_later,   ts_now,     ts_earlier));
+
+  // Edge cases
+  EXPECT_TRUE( TimeIsBetween(ts_earlier, ts_earlier, ts_earlier));
+  EXPECT_TRUE( TimeIsBetween(ts_earlier, ts_earlier, ts_later));
+  EXPECT_TRUE( TimeIsBetween(ts_earlier, ts_later,   ts_later));
+
+  // Earlier of two times
+  EXPECT_EQ(ts_earlier, TimeMin(ts_earlier, ts_earlier));
+  EXPECT_EQ(ts_earlier, TimeMin(ts_earlier, ts_now));
+  EXPECT_EQ(ts_earlier, TimeMin(ts_earlier, ts_later));
+  EXPECT_EQ(ts_earlier, TimeMin(ts_now,     ts_earlier));
+  EXPECT_EQ(ts_earlier, TimeMin(ts_later,   ts_earlier));
+
+  // Later of two times
+  EXPECT_EQ(ts_earlier, TimeMax(ts_earlier, ts_earlier));
+  EXPECT_EQ(ts_now,     TimeMax(ts_earlier, ts_now));
+  EXPECT_EQ(ts_later,   TimeMax(ts_earlier, ts_later));
+  EXPECT_EQ(ts_now,     TimeMax(ts_now,     ts_earlier));
+  EXPECT_EQ(ts_later,   TimeMax(ts_later,   ts_earlier));
+}
+
+TEST(TimeTest, Intervals) {
+  TimeStamp ts_earlier = Time();
+  TimeStamp ts_later = TimeAfter(500);
+
+  // We can't depend on ts_later and ts_earlier to be exactly 500 apart
+  // since time elapses between the calls to Time() and TimeAfter(500)
+  EXPECT_LE(500,  TimeDiff(ts_later, ts_earlier));
+  EXPECT_GE(-500, TimeDiff(ts_earlier, ts_later));
+
+  // Time has elapsed since ts_earlier
+  EXPECT_GE(TimeSince(ts_earlier), 0);
+
+  // ts_earlier is earlier than now, so TimeUntil ts_earlier is -ve
+  EXPECT_LE(TimeUntil(ts_earlier), 0);
+
+  // ts_later likely hasn't happened yet, so TimeSince could be -ve
+  // but within 500
+  EXPECT_GE(TimeSince(ts_later), -500);
+
+  // TimeUntil ts_later is at most 500
+  EXPECT_LE(TimeUntil(ts_later), 500);
+}
+
+TEST(TimeTest, BoundaryComparison) {
+  // Obtain two different times, in known order
+  TimeStamp ts_earlier = static_cast<TimeStamp>(-50);
+  TimeStamp ts_later = ts_earlier + 100;
+  EXPECT_NE(ts_earlier, ts_later);
+
+  // Common comparisons
+  EXPECT_TRUE( TimeIsLaterOrEqual(ts_earlier, ts_later));
+  EXPECT_TRUE( TimeIsLater(       ts_earlier, ts_later));
+  EXPECT_FALSE(TimeIsLaterOrEqual(ts_later,   ts_earlier));
+  EXPECT_FALSE(TimeIsLater(       ts_later,   ts_earlier));
+
+  // Earlier of two times
+  EXPECT_EQ(ts_earlier, TimeMin(ts_earlier, ts_earlier));
+  EXPECT_EQ(ts_earlier, TimeMin(ts_earlier, ts_later));
+  EXPECT_EQ(ts_earlier, TimeMin(ts_later,   ts_earlier));
+
+  // Later of two times
+  EXPECT_EQ(ts_earlier, TimeMax(ts_earlier, ts_earlier));
+  EXPECT_EQ(ts_later,   TimeMax(ts_earlier, ts_later));
+  EXPECT_EQ(ts_later,   TimeMax(ts_later,   ts_earlier));
+
+  // Interval
+  EXPECT_EQ(100,  TimeDiff(ts_later, ts_earlier));
+  EXPECT_EQ(-100, TimeDiff(ts_earlier, ts_later));
+}
+
+} // namespace talk_base
diff --git a/talk/base/urlencode_unittest.cc b/talk/base/urlencode_unittest.cc
new file mode 100644
index 0000000..f71cd75
--- /dev/null
+++ b/talk/base/urlencode_unittest.cc
@@ -0,0 +1,98 @@
+/*
+ * libjingle
+ * Copyright 2004--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/common.h"
+#include "talk/base/gunit.h"
+#include "talk/base/thread.h"
+#include "talk/base/urlencode.h"
+
+TEST(Urlencode, SourceTooLong) {
+  char source[] = "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"
+      "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^";
+  char dest[1];
+  ASSERT_EQ(0, UrlEncode(source, dest, ARRAY_SIZE(dest)));
+  ASSERT_EQ('\0', dest[0]);
+
+  dest[0] = 'a';
+  ASSERT_EQ(0, UrlEncode(source, dest, 0));
+  ASSERT_EQ('a', dest[0]);
+}
+
+TEST(Urlencode, OneCharacterConversion) {
+  char source[] = "^";
+  char dest[4];
+  ASSERT_EQ(3, UrlEncode(source, dest, ARRAY_SIZE(dest)));
+  ASSERT_STREQ("%5E", dest);
+}
+
+TEST(Urlencode, ShortDestinationNoEncoding) {
+  // In this case we have a destination that would not be
+  // big enough to hold an encoding but is big enough to
+  // hold the text given.
+  char source[] = "aa";
+  char dest[3];
+  ASSERT_EQ(2, UrlEncode(source, dest, ARRAY_SIZE(dest)));
+  ASSERT_STREQ("aa", dest);
+}
+
+TEST(Urlencode, ShortDestinationEncoding) {
+  // In this case we have a destination that is not
+  // big enough to hold the encoding.
+  char source[] = "&";
+  char dest[3];
+  ASSERT_EQ(0, UrlEncode(source, dest, ARRAY_SIZE(dest)));
+  ASSERT_EQ('\0', dest[0]);
+}
+
+TEST(Urlencode, Encoding1) {
+  char source[] = "A^ ";
+  char dest[8];
+  ASSERT_EQ(5, UrlEncode(source, dest, ARRAY_SIZE(dest)));
+  ASSERT_STREQ("A%5E+", dest);
+}
+
+TEST(Urlencode, Encoding2) {
+  char source[] = "A^ ";
+  char dest[8];
+  ASSERT_EQ(7, UrlEncodeWithoutEncodingSpaceAsPlus(source, dest,
+                                                   ARRAY_SIZE(dest)));
+  ASSERT_STREQ("A%5E%20", dest);
+}
+
+TEST(Urldecode, Decoding1) {
+  char source[] = "A%5E+";
+  char dest[8];
+  ASSERT_EQ(3, UrlDecode(source, dest));
+  ASSERT_STREQ("A^ ", dest);
+}
+
+TEST(Urldecode, Decoding2) {
+  char source[] = "A%5E+";
+  char dest[8];
+  ASSERT_EQ(3, UrlDecodeWithoutEncodingSpaceAsPlus(source, dest));
+  ASSERT_STREQ("A^+", dest);
+}
diff --git a/talk/examples/call/callclient.cc b/talk/examples/call/callclient.cc
index a698f36..c3a6490 100644
--- a/talk/examples/call/callclient.cc
+++ b/talk/examples/call/callclient.cc
@@ -54,6 +54,7 @@
 #include "talk/session/phone/mediasessionclient.h"
 #include "talk/session/phone/videorendererfactory.h"
 #include "talk/xmpp/constants.h"
+#include "talk/xmpp/hangoutpubsubclient.h"
 #include "talk/xmpp/mucroomconfigtask.h"
 #include "talk/xmpp/mucroomlookuptask.h"
 
@@ -94,11 +95,23 @@
 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"
+"  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"
+"";
+
+// TODO: Make present and record really work.
+const char* HANGOUT_COMMANDS =
+"Available MUC commands:\n"
+"\n"
+"  present    Starts presenting (just signalling; not actually presenting.)\n"
+"  unpresent  Stops presenting (just signalling; not actually presenting.)\n"
+"  record     Starts recording (just signalling; not actually recording.)\n"
+"  unrecord   Stops recording (just signalling; not actually recording.)\n"
+"  rmute [nick] Remote mute another participant.\n"
+"  quit       Quits the application.\n"
 "";
 
 const char* RECEIVE_COMMANDS =
@@ -169,13 +182,43 @@
       call_->Terminate();
     } else if (command == "mute") {
       call_->Mute(true);
+      if (InMuc()) {
+        hangout_pubsub_client_->PublishAudioMuteState(true);
+      }
     } else if (command == "unmute") {
       call_->Mute(false);
+      if (InMuc()) {
+        hangout_pubsub_client_->PublishAudioMuteState(false);
+      }
+    } else if (command == "present") {
+      if (InMuc()) {
+        hangout_pubsub_client_->PublishPresenterState(true);
+      }
+    } else if (command == "unpresent") {
+      if (InMuc()) {
+        hangout_pubsub_client_->PublishPresenterState(false);
+      }
+    } else if (command == "record") {
+      if (InMuc()) {
+        hangout_pubsub_client_->PublishRecordingState(true);
+      }
+    } else if (command == "unrecord") {
+      if (InMuc()) {
+        hangout_pubsub_client_->PublishRecordingState(false);
+      }
+    } else if ((command == "rmute") && (words.size() == 2)) {
+      if (InMuc()) {
+        const std::string& nick = words[1];
+        hangout_pubsub_client_->RemoteMute(nick);
+      }
     } else if ((command == "dtmf") && (words.size() == 2)) {
       int ev = std::string("0123456789*#").find(words[1][0]);
       call_->PressDTMF(ev);
     } else {
       console_->PrintLine(CALL_COMMANDS);
+      if (InMuc()) {
+        console_->PrintLine(HANGOUT_COMMANDS);
+      }
     }
   } else {
     if (command == "roster") {
@@ -231,6 +274,7 @@
       media_engine_(NULL),
       media_client_(NULL),
       call_(NULL),
+      hangout_pubsub_client_(NULL),
       incoming_call_(false),
       auto_accept_(false),
       pmuc_domain_("groupchat.google.com"),
@@ -295,6 +339,8 @@
     console_->PrintLine("call destroyed");
     call_ = NULL;
     session_ = NULL;
+    delete hangout_pubsub_client_;
+    hangout_pubsub_client_ = NULL;
   }
 }
 
@@ -366,7 +412,7 @@
       xmpp_client_->jid(),
       session_manager_,
       media_engine_,
-      new cricket::DeviceManager());
+      cricket::DeviceManagerFactory::Create());
   media_client_->SignalCallCreate.connect(this, &CallClient::OnCallCreate);
   media_client_->SignalCallDestroy.connect(this, &CallClient::OnCallDestroy);
   media_client_->SignalDevicesChange.connect(this,
@@ -615,6 +661,95 @@
       call_->SetVideoRenderer(session_, 0, remote_renderer_);
     }
   }
+  if (options.is_muc) {
+    const std::string& nick = mucs_[jid]->local_jid().resource();
+    hangout_pubsub_client_ =
+        new buzz::HangoutPubSubClient(xmpp_client_, jid, nick);
+    hangout_pubsub_client_->SignalPresenterStateChange.connect(
+        this, &CallClient::OnPresenterStateChange);
+    hangout_pubsub_client_->SignalAudioMuteStateChange.connect(
+        this, &CallClient::OnAudioMuteStateChange);
+    hangout_pubsub_client_->SignalRecordingStateChange.connect(
+        this, &CallClient::OnRecordingStateChange);
+    hangout_pubsub_client_->SignalRemoteMute.connect(
+        this, &CallClient::OnRemoteMuted);
+    hangout_pubsub_client_->SignalRequestError.connect(
+        this, &CallClient::OnHangoutRequestError);
+    hangout_pubsub_client_->SignalPublishAudioMuteError.connect(
+        this, &CallClient::OnHangoutPublishAudioMuteError);
+    hangout_pubsub_client_->SignalPublishPresenterError.connect(
+        this, &CallClient::OnHangoutPublishPresenterError);
+    hangout_pubsub_client_->SignalPublishRecordingError.connect(
+        this, &CallClient::OnHangoutPublishRecordingError);
+    hangout_pubsub_client_->SignalRemoteMuteError.connect(
+        this, &CallClient::OnHangoutRemoteMuteError);
+  }
+}
+
+void CallClient::OnPresenterStateChange(
+    const std::string& nick, bool was_presenting, bool is_presenting) {
+  if (!was_presenting && is_presenting) {
+    console_->PrintLine("%s now presenting.", nick.c_str());
+  } else if (was_presenting && !is_presenting) {
+    console_->PrintLine("%s no longer presenting.", nick.c_str());
+  }
+}
+
+void CallClient::OnAudioMuteStateChange(
+    const std::string& nick, bool was_muted, bool is_muted) {
+  if (!was_muted && is_muted) {
+    console_->PrintLine("%s now muted.", nick.c_str());
+  } else if (was_muted && !is_muted) {
+    console_->PrintLine("%s no longer muted.", nick.c_str());
+  }
+}
+
+void CallClient::OnRecordingStateChange(
+    const std::string& nick, bool was_recording, bool is_recording) {
+  if (!was_recording && is_recording) {
+    console_->PrintLine("%s now recording.", nick.c_str());
+  } else if (was_recording && is_recording) {
+    console_->PrintLine("%s no longer recording.", nick.c_str());
+  }
+}
+
+void CallClient::OnRemoteMuted(const std::string& mutee_nick,
+                               const std::string& muter_nick,
+                               bool should_mute_locally) {
+  if (should_mute_locally) {
+    call_->Mute(true);
+    console_->PrintLine("Remote muted by %s.", muter_nick.c_str());
+  } else {
+    console_->PrintLine("%s remote muted by %s.",
+                        mutee_nick.c_str(), muter_nick.c_str());
+  }
+}
+
+void CallClient::OnHangoutRequestError(const std::string& node,
+                                       const buzz::XmlElement* stanza) {
+  console_->PrintLine("Failed request pub sub items for node %s.",
+                      node.c_str());
+}
+
+void CallClient::OnHangoutPublishAudioMuteError(
+    const std::string& task_id, const buzz::XmlElement* stanza) {
+  console_->PrintLine("Failed to publish audio mute state.");
+}
+
+void CallClient::OnHangoutPublishPresenterError(
+    const std::string& task_id, const buzz::XmlElement* stanza) {
+  console_->PrintLine("Failed to publish presenting state.");
+}
+
+void CallClient::OnHangoutPublishRecordingError(
+    const std::string& task_id, const buzz::XmlElement* stanza) {
+  console_->PrintLine("Failed to publish recording state.");
+}
+
+void CallClient::OnHangoutRemoteMuteError(const std::string& task_id,
+                                          const std::string& mutee_nick,
+                                          const buzz::XmlElement* stanza) {
+  console_->PrintLine("Failed to remote mute.");
 }
 
 void CallClient::CallVoicemail(const std::string& name) {
@@ -624,7 +759,7 @@
     return;
   }
   buzz::VoicemailJidRequester *request =
-    new buzz::VoicemailJidRequester(xmpp_client_, jid, my_status_.jid());
+      new buzz::VoicemailJidRequester(xmpp_client_, jid, my_status_.jid());
   request->SignalGotVoicemailJid.connect(this,
                                          &CallClient::OnFoundVoicemailJid);
   request->SignalVoicemailJidError.connect(this,
@@ -847,13 +982,21 @@
   }
 }
 
+bool CallClient::InMuc() {
+  return FirstMucJid().IsValid();
+}
+
+const buzz::Jid& CallClient::FirstMucJid() {
+  return mucs_.begin()->first;
+}
+
 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;
+    room_jid = FirstMucJid();
   }
 
   if (!room_jid.IsValid()) {
diff --git a/talk/examples/call/callclient.h b/talk/examples/call/callclient.h
index 18296be..568a818 100644
--- a/talk/examples/call/callclient.h
+++ b/talk/examples/call/callclient.h
@@ -36,6 +36,7 @@
 #include "talk/session/phone/mediachannel.h"
 #include "talk/session/phone/mediamessages.h"
 #include "talk/session/phone/mediasessionclient.h"
+#include "talk/xmpp/hangoutpubsubclient.h"
 #include "talk/xmpp/xmppclient.h"
 #include "talk/examples/call/status.h"
 #include "talk/examples/call/console.h"
@@ -55,6 +56,7 @@
 class MucRoomLookupTask;
 class MucStatus;
 class XmlElement;
+class HangoutPubSubClient;
 struct AvailableMediaEntry;
 struct MucRoomInfo;
 }
@@ -124,6 +126,8 @@
   void JoinMuc(const std::string& room_jid_str);
   void LookupAndJoinMuc(const std::string& room_name);
   void InviteToMuc(const std::string& user, const std::string& room);
+  bool InMuc();
+  const buzz::Jid& FirstMucJid();
   void LeaveMuc(const std::string& room);
   void SetNick(const std::string& muc_nick);
   void SetPortAllocatorFlags(uint32 flags) { portallocator_flags_ = flags; }
@@ -167,6 +171,26 @@
   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 OnPresenterStateChange(const std::string& nick,
+                              bool was_presenting, bool is_presenting);
+  void OnAudioMuteStateChange(const std::string& nick,
+                              bool was_muted, bool is_muted);
+  void OnRecordingStateChange(const std::string& nick,
+                              bool was_recording, bool is_recording);
+  void OnRemoteMuted(const std::string& mutee_nick,
+                     const std::string& muter_nick,
+                     bool should_mute_locally);
+  void OnHangoutRequestError(const std::string& node,
+                             const buzz::XmlElement* stanza);
+  void OnHangoutPublishAudioMuteError(const std::string& task_id,
+                                      const buzz::XmlElement* stanza);
+  void OnHangoutPublishPresenterError(const std::string& task_id,
+                                      const buzz::XmlElement* stanza);
+  void OnHangoutPublishRecordingError(const std::string& task_id,
+                                      const buzz::XmlElement* stanza);
+  void OnHangoutRemoteMuteError(const std::string& task_id,
+                                const std::string& mutee_nick,
+                                const buzz::XmlElement* stanza);
   void OnDevicesChange();
   void OnFoundVoicemailJid(const buzz::Jid& to, const buzz::Jid& voicemail);
   void OnVoicemailJidError(const buzz::Jid& to);
@@ -224,6 +248,7 @@
 
   cricket::Call* call_;
   cricket::Session *session_;
+  buzz::HangoutPubSubClient* hangout_pubsub_client_;
   bool incoming_call_;
   bool auto_accept_;
   std::string pmuc_domain_;
diff --git a/talk/libjingle.scons b/talk/libjingle.scons
index e053a8f..5e029b0 100644
--- a/talk/libjingle.scons
+++ b/talk/libjingle.scons
@@ -138,6 +138,7 @@
                "base/httpclient.cc",
                "base/httpcommon.cc",
                "base/httprequest.cc",
+               "base/httpserver.cc",
                "base/logging.cc",
                "base/md5c.c",
                "base/messagehandler.cc",
@@ -226,9 +227,13 @@
                "xmpp/constants.cc",
                "xmpp/iqtask.cc",
                "xmpp/jid.cc",
+               "xmpp/pubsubtasks.cc",
+               "xmpp/pubsubclient.cc",
+               "xmpp/hangoutpubsubclient.cc",
                "xmpp/mucroomconfigtask.cc",
                "xmpp/mucroomlookuptask.cc",
                "xmpp/ratelimitmanager.cc",
+               "xmpp/receivetask.cc",
                "xmpp/saslmechanism.cc",
                "xmpp/xmppclient.cc",
                "xmpp/xmppengineimpl.cc",
@@ -255,6 +260,9 @@
                "base/winping.cc",
                "session/phone/gdivideorenderer.cc",
              ],
+             mac_ccflags = [
+               "-Wno-deprecated-declarations",
+             ],
              extra_srcs = [
                "base/json.cc",
              ],
@@ -373,12 +381,34 @@
            "p2p/base/stunserver_main.cc",
          ],
 )
-talk.Unittest(env, name = "network",
+talk.Unittest(env, name = "base",
               libs = [
                 "jingle",
               ],
               srcs = [
+                "base/asynchttprequest_unittest.cc",
+                "base/autodetectproxy_unittest.cc",
+                "base/bytebuffer_unittest.cc",
+                "base/event_unittest.cc",
+                "base/fileutils_unittest.cc",
+                "base/helpers_unittest.cc",
+                "base/host_unittest.cc",
+                "base/httpbase_unittest.cc",
+                "base/httpcommon_unittest.cc",
+                "base/httpserver_unittest.cc",
+                "base/logging_unittest.cc",
+                "base/messagequeue_unittest.cc",
                 "base/network_unittest.cc",
+                "base/referencecountedsingletonfactory_unittest.cc",
+                "base/signalthread_unittest.cc",
+                "base/socketaddress_unittest.cc",
+                "base/stream_unittest.cc",
+                "base/stringencode_unittest.cc",
+                "base/stringutils_unittest.cc",
+                "base/task_unittest.cc",
+                "base/thread_unittest.cc",
+                "base/time_unittest.cc",
+                "base/urlencode_unittest.cc",
               ],
               includedirs = [
                 "third_party/gtest/include",
diff --git a/talk/p2p/client/sessionsendtask.h b/talk/p2p/client/sessionsendtask.h
index ea1e4f0..c9008d4 100644
--- a/talk/p2p/client/sessionsendtask.h
+++ b/talk/p2p/client/sessionsendtask.h
@@ -108,10 +108,6 @@
   }
 
   virtual int ProcessResponse() {
-    if (parent_->GetState() != buzz::XmppEngine::STATE_OPEN) {
-      return STATE_DONE;
-    }
-
     const buzz::XmlElement* next = NextStanza();
     if (next == NULL)
       return STATE_BLOCKED;
diff --git a/talk/session/phone/channelmanager.cc b/talk/session/phone/channelmanager.cc
index 97419cb..98b8ded 100644
--- a/talk/session/phone/channelmanager.cc
+++ b/talk/session/phone/channelmanager.cc
@@ -132,12 +132,12 @@
 
 ChannelManager::ChannelManager(talk_base::Thread* worker_thread)
     : media_engine_(MediaEngineFactory::Create()),
-      device_manager_(new DeviceManager()),
+      device_manager_(cricket::DeviceManagerFactory::Create()),
       initialized_(false),
       main_thread_(talk_base::Thread::Current()),
       worker_thread_(worker_thread),
-      audio_in_device_(DeviceManager::kDefaultDeviceName),
-      audio_out_device_(DeviceManager::kDefaultDeviceName),
+      audio_in_device_(DeviceManagerInterface::kDefaultDeviceName),
+      audio_out_device_(DeviceManagerInterface::kDefaultDeviceName),
       audio_options_(MediaEngineInterface::DEFAULT_AUDIO_OPTIONS),
       local_renderer_(NULL),
       capturing_(false),
@@ -145,15 +145,16 @@
   Construct();
 }
 
-ChannelManager::ChannelManager(MediaEngineInterface* me, DeviceManager* dm,
+ChannelManager::ChannelManager(MediaEngineInterface* me,
+                               DeviceManagerInterface* 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_in_device_(DeviceManagerInterface::kDefaultDeviceName),
+      audio_out_device_(DeviceManagerInterface::kDefaultDeviceName),
       audio_options_(MediaEngineInterface::DEFAULT_AUDIO_OPTIONS),
       local_renderer_(NULL),
       capturing_(false),
@@ -166,7 +167,7 @@
   SignalDevicesChange.repeat(device_manager_->SignalDevicesChange);
   device_manager_->Init();
   // Set camera_device_ to the name of the default video capturer.
-  SetVideoOptions(DeviceManager::kDefaultDeviceName);
+  SetVideoOptions(DeviceManagerInterface::kDefaultDeviceName);
 
   // Camera is started asynchronously, request callbacks when startup
   // completes to be able to forward them to the rendering manager.
@@ -227,19 +228,19 @@
       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;
+        audio_in_device_ = DeviceManagerInterface::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;
+        audio_out_device_ = DeviceManagerInterface::kDefaultDeviceName;
       }
       if (!device_manager_->GetVideoCaptureDevice(camera_device_, &device)) {
         if (!camera_device_.empty()) {
           LOG(LS_WARNING) << "The preferred camera '" << camera_device_
                           << "' is unavailable. Fall back to the default.";
         }
-        camera_device_ = DeviceManager::kDefaultDeviceName;
+        camera_device_ = DeviceManagerInterface::kDefaultDeviceName;
       }
 
       if (!SetAudioOptions(audio_in_device_, audio_out_device_,
diff --git a/talk/session/phone/channelmanager.h b/talk/session/phone/channelmanager.h
index cada408..c574a43 100644
--- a/talk/session/phone/channelmanager.h
+++ b/talk/session/phone/channelmanager.h
@@ -58,7 +58,7 @@
   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(MediaEngineInterface* me, DeviceManager* dm,
+  ChannelManager(MediaEngineInterface* me, DeviceManagerInterface* dm,
                  talk_base::Thread* worker);
   ~ChannelManager();
 
@@ -181,7 +181,7 @@
 
   talk_base::CriticalSection crit_;
   talk_base::scoped_ptr<MediaEngineInterface> media_engine_;
-  talk_base::scoped_ptr<DeviceManager> device_manager_;
+  talk_base::scoped_ptr<DeviceManagerInterface> device_manager_;
   bool initialized_;
   talk_base::Thread* main_thread_;
   talk_base::Thread* worker_thread_;
diff --git a/talk/session/phone/codec.cc b/talk/session/phone/codec.cc
index 01d1a7b..a200ca4 100644
--- a/talk/session/phone/codec.cc
+++ b/talk/session/phone/codec.cc
@@ -26,28 +26,33 @@
  */
 
 #include "talk/session/phone/codec.h"
+
 #include <sstream>
 
+#include "talk/base/stringutils.h"
+
 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);
+  // Matching is case-insensitive.
+  return (payload <= kMaxStaticPayloadId) ?
+      (id == payload) : (_stricmp(name.c_str(), nm.c_str()) == 0);
 }
 
 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.
+  // unless the codec is VBR (0), 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.bitrate == 0 || bitrate <= 0 || bitrate == codec.bitrate) &&
       (codec.channels == 0 || channels == codec.channels);
 }
 
@@ -60,7 +65,9 @@
 
 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);
+  // Matching is case-insensitive.
+  return (payload <= kMaxStaticPayloadId) ?
+      (id == payload) : (_stricmp(name.c_str(), nm.c_str()) == 0);
 }
 
 bool VideoCodec::Matches(const VideoCodec& codec) const {
diff --git a/talk/session/phone/devicemanager.cc b/talk/session/phone/devicemanager.cc
index 376b0e9..a464dd1 100644
--- a/talk/session/phone/devicemanager.cc
+++ b/talk/session/phone/devicemanager.cc
@@ -65,7 +65,11 @@
 
 namespace cricket {
 // Initialize to empty string.
-const char DeviceManager::kDefaultDeviceName[] = "";
+const char DeviceManagerInterface::kDefaultDeviceName[] = "";
+
+DeviceManagerInterface* DeviceManagerFactory::Create() {
+  return new DeviceManager();
+}
 
 #ifdef WIN32
 class DeviceWatcher : public talk_base::Win32Window {
diff --git a/talk/session/phone/devicemanager.h b/talk/session/phone/devicemanager.h
index 04e494e..38776fa 100644
--- a/talk/session/phone/devicemanager.h
+++ b/talk/session/phone/devicemanager.h
@@ -55,9 +55,42 @@
   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 {
+// DeviceManagerInterface - interface to manage the audio and
+// video devices on the system.
+class DeviceManagerInterface {
+ public:
+  virtual ~DeviceManagerInterface() { }
+
+  // Initialization
+  virtual bool Init() = 0;
+  virtual void Terminate() = 0;
+
+  // Capabilities
+  virtual int GetCapabilities() = 0;
+
+  // Device enumeration
+  virtual bool GetAudioInputDevices(std::vector<Device>* devices) = 0;
+  virtual bool GetAudioOutputDevices(std::vector<Device>* devices) = 0;
+
+  virtual bool GetAudioInputDevice(const std::string& name, Device* out) = 0;
+  virtual bool GetAudioOutputDevice(const std::string& name, Device* out) = 0;
+
+  virtual bool GetVideoCaptureDevices(std::vector<Device>* devs) = 0;
+  virtual bool GetVideoCaptureDevice(const std::string& name, Device* out) = 0;
+
+  sigslot::signal0<> SignalDevicesChange;
+
+  static const char kDefaultDeviceName[];
+};
+
+class DeviceManagerFactory {
+ public:
+  static DeviceManagerInterface* Create();
+ private:
+  DeviceManagerFactory();
+};
+
+class DeviceManager : public DeviceManagerInterface {
  public:
   DeviceManager();
   virtual ~DeviceManager();
@@ -65,7 +98,6 @@
   // Initialization
   virtual bool Init();
   virtual void Terminate();
-  bool initialized() const { return initialized_; }
 
   // Capabilities
   virtual int GetCapabilities();
@@ -74,18 +106,15 @@
   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 GetAudioInputDevice(const std::string& name, Device* out);
+  virtual bool GetAudioOutputDevice(const std::string& name, Device* out);
 
   virtual bool GetVideoCaptureDevices(std::vector<Device>* devs);
-  bool GetVideoCaptureDevice(const std::string& name, Device* out);
+  virtual bool GetVideoCaptureDevice(const std::string& name, Device* out);
 
-  sigslot::signal0<> SignalDevicesChange;
-
+  bool initialized() const { return initialized_; }
   void OnDevicesChange() { SignalDevicesChange(); }
 
-  static const char kDefaultDeviceName[];
-
  protected:
   virtual bool GetAudioDevice(bool is_input, const std::string& name,
                               Device* out);
diff --git a/talk/session/phone/mediaengine.cc b/talk/session/phone/mediaengine.cc
index 5190232..51e3b8b 100644
--- a/talk/session/phone/mediaengine.cc
+++ b/talk/session/phone/mediaengine.cc
@@ -30,18 +30,31 @@
 #if defined(HAVE_LINPHONE)
 #include "talk/session/phone/linphonemediaengine.h"
 #else
+#if defined(HAVE_WEBRTC_VOICE)
+#include "talk/session/phone/webrtcvoiceengine.h"
+#endif  // HAVE_WEBRTC_VOICE
+#if defined(HAVE_WEBRTC_VIDEO)
+#include "talk/session/phone/webrtcvideoengine.h"
+#endif  // HAVE_WEBRTC_VIDEO
 #endif  // HAVE_LINPHONE
 
 namespace cricket {
+#if defined(HAVE_WEBRTC_VOICE)
+#define AUDIO_ENG_NAME WebRtcVoiceEngine
+#endif
+
+#if defined(HAVE_WEBRTC_VIDEO)
+#define VIDEO_ENG_NAME WebRtcVideoEngine
+#endif
 
 MediaEngineInterface* MediaEngineFactory::Create() {
 #if defined(HAVE_LINPHONE)
   return new LinphoneMediaEngine("", "");
-#elif defined(ANDROID)
-  return AndroidMediaEngineFactory::Create();
+#elif defined(AUDIO_ENG_NAME) && defined(VIDEO_ENG_NAME)
+  return new CompositeMediaEngine<AUDIO_ENG_NAME, VIDEO_ENG_NAME>();
 #else
   return new NullMediaEngine();
-#endif  // HAVE_LINPHONE ANDROID HAVE_LMI HAVE_GIPS HAVE_WEBRTC_VOICE/VIDEO
+#endif
 }
 
 };  // namespace cricket
diff --git a/talk/session/phone/mediasession.cc b/talk/session/phone/mediasession.cc
index d70ab16..8102d2a 100644
--- a/talk/session/phone/mediasession.cc
+++ b/talk/session/phone/mediasession.cc
@@ -56,6 +56,7 @@
   return true;
 }
 
+#ifdef HAVE_SRTP
 static bool AddCryptoParams(const std::string& cipher_suite,
                             CryptoParamsVec *out) {
   int size = out->size();
@@ -63,6 +64,7 @@
   out->resize(size + 1);
   return CreateCryptoParams(size, cipher_suite, &out->at(size));
 }
+#endif
 
 // For audio, HMAC 32 is prefered because of the low overhead.
 static bool GetSupportedAudioCryptos(CryptoParamsVec* cryptos) {
diff --git a/talk/session/phone/mediasessionclient.cc b/talk/session/phone/mediasessionclient.cc
index e709479..3491d26 100644
--- a/talk/session/phone/mediasessionclient.cc
+++ b/talk/session/phone/mediasessionclient.cc
@@ -55,7 +55,7 @@
 
 MediaSessionClient::MediaSessionClient(
     const buzz::Jid& jid, SessionManager *manager,
-    MediaEngineInterface* media_engine, DeviceManager* device_manager)
+    MediaEngineInterface* media_engine, DeviceManagerInterface* device_manager)
     : jid_(jid),
       session_manager_(manager),
       focus_call_(NULL),
diff --git a/talk/session/phone/mediasessionclient.h b/talk/session/phone/mediasessionclient.h
index 4616589..c5c455f 100644
--- a/talk/session/phone/mediasessionclient.h
+++ b/talk/session/phone/mediasessionclient.h
@@ -56,7 +56,7 @@
   // and device_manager.
   MediaSessionClient(const buzz::Jid& jid, SessionManager *manager,
                      MediaEngineInterface* media_engine,
-                     DeviceManager* device_manager);
+                     DeviceManagerInterface* device_manager);
   ~MediaSessionClient();
 
   const buzz::Jid &jid() const { return jid_; }
diff --git a/talk/session/phone/v4llookup.cc b/talk/session/phone/v4llookup.cc
index e8436e1..f68d219 100644
--- a/talk/session/phone/v4llookup.cc
+++ b/talk/session/phone/v4llookup.cc
@@ -22,7 +22,7 @@
 
 namespace cricket {
 
-V4LLookup *V4LLookup::v4l_lookup_ = new V4LLookup();
+V4LLookup *V4LLookup::v4l_lookup_ = NULL;
 
 bool V4LLookup::CheckIsV4L2Device(const std::string& device_path) {
   // check device major/minor numbers are in the range for video devices.
diff --git a/talk/session/phone/v4llookup.h b/talk/session/phone/v4llookup.h
index 1150172..966d6c5 100644
--- a/talk/session/phone/v4llookup.h
+++ b/talk/session/phone/v4llookup.h
@@ -14,27 +14,31 @@
 #ifdef LINUX
 namespace cricket {
 class V4LLookup {
-  public:
-    virtual ~V4LLookup() {}
+ public:
+  virtual ~V4LLookup() {}
 
-    static bool IsV4L2Device(const std::string& device_path) {
-      return GetV4LLookup()->CheckIsV4L2Device(device_path);
+  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() {
+    if (!v4l_lookup_) {
+      v4l_lookup_ = new V4LLookup();
     }
+    return v4l_lookup_;
+  }
 
-    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);
-
+ 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  // LINUX
 #endif  // TALK_SESSION_PHONE_V4LLOOKUP_H_
diff --git a/talk/session/phone/webrtccommon.h b/talk/session/phone/webrtccommon.h
index 60fee27..286b049 100644
--- a/talk/session/phone/webrtccommon.h
+++ b/talk/session/phone/webrtccommon.h
@@ -31,12 +31,8 @@
 
 #ifdef WEBRTC_RELATIVE_PATH
 #include "common_types.h"
-#include "video_engine/main/interface/vie_base.h"
-#include "voice_engine/main/interface/voe_base.h"
 #else
 #include "third_party/webrtc/files/include/common_types.h"
-#include "third_party/webrtc/files/include/voe_base.h"
-#include "third_party/webrtc/files/include/vie_base.h"
 #endif  // WEBRTC_RELATIVE_PATH
 
 namespace cricket {
diff --git a/talk/session/phone/webrtcmediaengine.h b/talk/session/phone/webrtcmediaengine.h
new file mode 100644
index 0000000..2173d14
--- /dev/null
+++ b/talk/session/phone/webrtcmediaengine.h
@@ -0,0 +1,61 @@
+/*
+ * 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_SESSION_PHONE_WEBRTCMEDIAENGINE_H_
+#define TALK_SESSION_PHONE_WEBRTCMEDIAENGINE_H_
+
+#include "talk/session/phone/mediaengine.h"
+#include "talk/session/phone/webrtcvideoengine.h"
+#include "talk/session/phone/webrtcvoiceengine.h"
+
+namespace webrtc {
+class AudioDeviceModule;
+class VideoCaptureModule;
+}
+
+namespace cricket {
+
+typedef CompositeMediaEngine<WebRtcVoiceEngine, WebRtcVideoEngine>
+        WebRtcCompositeMediaEngine;
+
+class WebRtcMediaEngine : public WebRtcCompositeMediaEngine {
+ public:
+  WebRtcMediaEngine(webrtc::AudioDeviceModule* adm,
+      webrtc::AudioDeviceModule* adm_sc, webrtc::VideoCaptureModule* vcm) {
+    voice_.SetAudioDeviceModule(adm, adm_sc);
+    video_.SetVoiceEngine(&voice_);
+    video_.SetCaptureModule(vcm);
+    video_.EnableTimedRender();
+  }
+  // Allow the VCM be set later if not ready during the construction time
+  bool SetVideoCaptureModule(webrtc::VideoCaptureModule* vcm) {
+    return video_.SetCaptureModule(vcm);
+  }
+};
+
+}  // namespace cricket
+#endif  // TALK_SESSION_PHONE_WEBRTCMEDIAENGINE_H_
diff --git a/talk/session/phone/webrtcvideoengine.cc b/talk/session/phone/webrtcvideoengine.cc
index 6f9ff6a..a65c264 100644
--- a/talk/session/phone/webrtcvideoengine.cc
+++ b/talk/session/phone/webrtcvideoengine.cc
@@ -62,15 +62,12 @@
     return renderer_->SetSize(width_, height_, 0) ? 0 : -1;
   }
 
-  virtual int DeliverFrame(unsigned char* buffer, int buffer_size) {
+  virtual int DeliverFrame(unsigned char* buffer, int buffer_size,
+                           unsigned int time_stamp) {
     if (renderer_ == NULL)
       return 0;
     WebRtcVideoFrame video_frame;
-    // TODO: Currently by the time DeliverFrame got called,
-    // ViE expects the frame will be rendered ASAP. However, the libjingle
-    // renderer may have its own internal delays. Can you disable the buffering
-    // inside ViE and surface the timing information to this callback?
-    video_frame.Attach(buffer, buffer_size, width_, height_, 0, 0);
+    video_frame.Attach(buffer, buffer_size, width_, height_, 0, time_stamp);
     int ret = renderer_->RenderFrame(&video_frame) ? 0 : -1;
     uint8* buffer_temp;
     size_t buffer_size_temp;
@@ -117,38 +114,24 @@
 
 WebRtcVideoEngine::WebRtcVideoEngine()
     : vie_wrapper_(new ViEWrapper()),
-      capture_module_(NULL),
-      external_capture_(false),
-      render_module_(new WebRtcPassthroughRender()),
       voice_engine_(NULL) {
   Construct();
 }
 
 WebRtcVideoEngine::WebRtcVideoEngine(WebRtcVoiceEngine* voice_engine,
-                                     webrtc::VideoCaptureModule* capture)
-    : vie_wrapper_(new ViEWrapper()),
-      capture_module_(capture),
-      external_capture_(true),
-      render_module_(webrtc::VideoRender::CreateVideoRender(0, NULL, false,
-          webrtc::kRenderExternal)),
-      voice_engine_(voice_engine) {
-  Construct();
-}
-
-WebRtcVideoEngine::WebRtcVideoEngine(WebRtcVoiceEngine* voice_engine,
                                      ViEWrapper* vie_wrapper)
     : vie_wrapper_(vie_wrapper),
-      capture_module_(NULL),
-      external_capture_(false),
-      render_module_(new WebRtcPassthroughRender()),
       voice_engine_(voice_engine) {
   Construct();
 }
 
 void  WebRtcVideoEngine::Construct() {
+  initialized_ = false;
   capture_id_ = -1;
+  capture_module_ = NULL;
   log_level_ = kDefaultLogSeverity;
   capture_started_ = false;
+  render_module_.reset(new WebRtcPassthroughRender());
 
   ApplyLogging();
   if (vie_wrapper_->engine()->SetTraceCallback(this) != 0) {
@@ -220,6 +203,8 @@
 
   std::sort(video_codecs_.begin(), video_codecs_.end(),
             &VideoCodec::Preferable);
+
+  initialized_ = true;
   return true;
 }
 
@@ -304,6 +289,7 @@
 
 void WebRtcVideoEngine::Terminate() {
   LOG(LS_INFO) << "WebRtcVideoEngine::Terminate";
+  initialized_ = false;
   SetCapture(false);
   if (local_renderer_.get()) {
     // If the renderer already set, stop it first
@@ -590,6 +576,25 @@
   }
 }
 
+bool WebRtcVideoEngine::SetVoiceEngine(WebRtcVoiceEngine* voice_engine) {
+  if (initialized_) {
+    LOG(LS_WARNING) << "SetVoiceEngine can not be called after Init.";
+    return false;
+  }
+  voice_engine_ = voice_engine;
+  return true;
+}
+
+bool WebRtcVideoEngine::EnableTimedRender() {
+  if (initialized_) {
+    LOG(LS_WARNING) << "EnableTimedRender can not be called after Init.";
+    return false;
+  }
+  render_module_.reset(webrtc::VideoRender::CreateVideoRender(0, NULL,
+      false, webrtc::kRenderExternal));
+  return true;
+}
+
 // WebRtcVideoMediaChannel
 
 WebRtcVideoMediaChannel::WebRtcVideoMediaChannel(
diff --git a/talk/session/phone/webrtcvideoengine.h b/talk/session/phone/webrtcvideoengine.h
index 8daa81c..dc69a83 100644
--- a/talk/session/phone/webrtcvideoengine.h
+++ b/talk/session/phone/webrtcvideoengine.h
@@ -35,6 +35,11 @@
 #include "talk/session/phone/codec.h"
 #include "talk/session/phone/channel.h"
 #include "talk/session/phone/webrtccommon.h"
+#ifdef WEBRTC_RELATIVE_PATH
+#include "voice_engine/main/interface/vie_base.h"
+#else
+#include "third_party/webrtc/files/include/vie_base.h"
+#endif  // WEBRTC_RELATIVE_PATH
 
 namespace webrtc {
 class VideoCaptureModule;
@@ -56,10 +61,6 @@
  public:
   // Creates the WebRtcVideoEngine with internal VideoCaptureModule.
   WebRtcVideoEngine();
-  // Creates the WebRtcVideoEngine, and specifies the WebRtcVoiceEngine and
-  // external VideoCaptureModule to use.
-  WebRtcVideoEngine(WebRtcVoiceEngine* voice_engine,
-                    webrtc::VideoCaptureModule* capture);
   // For testing purposes. Allows the WebRtcVoiceEngine and
   // ViEWrapper to be mocks.
   WebRtcVideoEngine(WebRtcVoiceEngine* voice_engine, ViEWrapper* vie_wrapper);
@@ -90,6 +91,12 @@
 
   int GetLastEngineError();
 
+  // Set the VoiceEngine for A/V sync. This can only be called before Init.
+  bool SetVoiceEngine(WebRtcVoiceEngine* voice_engine);
+
+  // Enable the render module with timing control.
+  bool EnableTimedRender();
+
   VideoEncoderConfig& default_encoder_config() {
     return default_encoder_config_;
   }
@@ -138,6 +145,7 @@
   VideoEncoderConfig default_encoder_config_;
   bool capture_started_;
   talk_base::scoped_ptr<WebRtcRenderAdapter> local_renderer_;
+  bool initialized_;
 };
 
 class WebRtcVideoMediaChannel : public VideoMediaChannel,
diff --git a/talk/session/phone/webrtcvoiceengine.cc b/talk/session/phone/webrtcvoiceengine.cc
index a53dfc1..561b025 100644
--- a/talk/session/phone/webrtcvoiceengine.cc
+++ b/talk/session/phone/webrtcvoiceengine.cc
@@ -41,6 +41,7 @@
 #include "talk/base/helpers.h"
 #include "talk/base/logging.h"
 #include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
 #include "talk/session/phone/webrtcvoe.h"
 
 #ifdef WIN32
@@ -76,6 +77,23 @@
 static const char kRtpAudioLevelHeaderExtension[] =
     "urn:ietf:params:rtp-hdrext:ssrc-audio-level";
 
+static const char kIsacCodecName[] = "ISAC";
+static const char kL16CodecName[] = "L16";
+
+// Dumps an AudioCodec in RFC 2327-ish format.
+static std::string ToString(const AudioCodec& codec) {
+  std::stringstream ss;
+  ss << codec.name << "/" << codec.clockrate << "/" << codec.channels
+     << " (" << codec.id << ")";
+  return ss.str();
+}
+static std::string ToString(const webrtc::CodecInst& codec) {
+  std::stringstream ss;
+  ss << codec.plname << "/" << codec.plfreq << "/" << codec.channels
+     << " (" << codec.pltype << ")";
+  return ss.str();
+}
+
 static void LogMultiline(talk_base::LoggingSeverity sev, char* text) {
   const char* delim = "\r\n";
   for (char* tok = strtok(text, delim); tok; tok = strtok(NULL, delim)) {
@@ -83,23 +101,21 @@
   }
 }
 
-static const char kL16CodecName[] = "L16";
-
 // WebRtcVoiceEngine
 const WebRtcVoiceEngine::CodecPref WebRtcVoiceEngine::kCodecPrefs[] = {
-  { "ISAC",   16000 },
-  { "ISAC",   32000 },
-  { "speex",  16000 },
-  { "G722",   16000 },
-  { "iLBC",   8000 },
-  { "speex",  8000 },
-  { "PCMU",   8000 },
-  { "PCMA",   8000 },
-  { "CN",     32000 },
-  { "CN",     16000 },
-  { "CN",     8000 },
-  { "red",    8000 },
-  { "telephone-event", 8000 },
+  { "ISAC",   16000,  103 },
+  { "ISAC",   32000,  104 },
+  { "speex",  16000,  107 },
+  { "G722",   16000,  9 },
+  { "ILBC",   8000,   102 },
+  { "speex",  8000,   108 },
+  { "PCMU",   8000,   0 },
+  { "PCMA",   8000,   8 },
+  { "CN",     32000,  106 },
+  { "CN",     16000,  105 },
+  { "CN",     8000,   13 },
+  { "red",    8000,   127 },
+  { "telephone-event", 8000, 126 },
 };
 
 class WebRtcSoundclipMedia : public SoundclipMedia {
@@ -191,19 +207,6 @@
   Construct();
 }
 
-WebRtcVoiceEngine::WebRtcVoiceEngine(webrtc::AudioDeviceModule* adm,
-                                     webrtc::AudioDeviceModule* adm_sc)
-    : voe_wrapper_(new VoEWrapper()),
-      voe_wrapper_sc_(new VoEWrapper()),
-      tracing_(new VoETraceWrapper()),
-      adm_(adm),
-      adm_sc_(adm_sc),
-      log_level_(kDefaultLogSeverity),
-      is_dumping_aec_(false),
-      desired_local_monitor_enable_(false) {
-  Construct();
-}
-
 WebRtcVoiceEngine::WebRtcVoiceEngine(VoEWrapper* voe_wrapper,
                                      VoEWrapper* voe_wrapper_sc,
                                      VoETraceWrapper* tracing)
@@ -231,28 +234,48 @@
   // Clear the default agc state.
   memset(&default_agc_config_, 0, sizeof(default_agc_config_));
 
-  // Load our audio codec list
+  // Load our audio codec list.
+  ConstructCodecs();
+}
+
+void WebRtcVoiceEngine::ConstructCodecs() {
   LOG(LS_INFO) << "WebRtc VoiceEngine codecs:";
   int ncodecs = voe_wrapper_->codec()->NumOfCodecs();
   for (int i = 0; i < ncodecs; ++i) {
-    webrtc::CodecInst gcodec;
-    if (voe_wrapper_->codec()->GetCodec(i, gcodec) >= 0) {
-      // Skip the codecs that we don't support.
-      if (strcmp(gcodec.plname, kL16CodecName) == 0) {
+    webrtc::CodecInst voe_codec;
+    if (voe_wrapper_->codec()->GetCodec(i, voe_codec) >= 0) {
+      // Skip uncompressed formats.
+      if (_stricmp(voe_codec.plname, kL16CodecName) == 0) {
         continue;
       }
-      int pref = GetCodecPreference(gcodec.plname, gcodec.plfreq);
-      if (pref != -1) {
-        if (gcodec.rate == -1) gcodec.rate = 0;
-        AudioCodec codec(gcodec.pltype, gcodec.plname, gcodec.plfreq,
-                         gcodec.rate, gcodec.channels, pref);
-        LOG(LS_INFO) << gcodec.plname << "/" << gcodec.plfreq << "/" \
-                     << gcodec.channels << " " << gcodec.pltype;
+
+      const CodecPref* pref = NULL;
+      for (size_t j = 0; j < ARRAY_SIZE(kCodecPrefs); ++j) {
+        if (_stricmp(kCodecPrefs[j].name, voe_codec.plname) == 0 &&
+            kCodecPrefs[j].clockrate == voe_codec.plfreq) {
+          pref = &kCodecPrefs[j];
+          break;
+        }
+      }
+
+      if (pref) {
+        // Use the payload type that we've configured in our pref table;
+        // use the offset in our pref table to determine the sort order.
+        AudioCodec codec(pref->payload_type, voe_codec.plname, voe_codec.plfreq,
+                         voe_codec.rate, voe_codec.channels,
+                         ARRAY_SIZE(kCodecPrefs) - (pref - kCodecPrefs));
+        LOG(LS_INFO) << ToString(codec);
+        // For ISAC, use 0 to indicate auto bandwidth in our signaling.
+        if (_stricmp(codec.name.c_str(), kIsacCodecName) == 0) {
+          codec.bitrate = 0;
+        }
         codecs_.push_back(codec);
+      } else {
+        LOG(LS_WARNING) << "Unexpected codec: " << ToString(voe_codec);
       }
     }
   }
-  // Make sure they are in local preference order
+  // Make sure they are in local preference order.
   std::sort(codecs_.begin(), codecs_.end(), &AudioCodec::Preferable);
 }
 
@@ -340,8 +363,7 @@
   LOG(LS_INFO) << "WebRtc VoiceEngine codecs:";
   for (std::vector<AudioCodec>::const_iterator it = codecs_.begin();
       it != codecs_.end(); ++it) {
-    LOG(LS_INFO) << it->name << "/" << it->clockrate << "/"
-              << it->channels << " " << it->id;
+    LOG(LS_INFO) << ToString(*it);
   }
 
 #if defined(LINUX) && !defined(HAVE_LIBPULSE)
@@ -701,21 +723,29 @@
   return FindWebRtcCodec(in, NULL);
 }
 
+// Get the VoiceEngine codec that matches |in|, with the supplied settings.
 bool WebRtcVoiceEngine::FindWebRtcCodec(const AudioCodec& in,
                                         webrtc::CodecInst* out) {
   int ncodecs = voe_wrapper_->codec()->NumOfCodecs();
   for (int i = 0; i < ncodecs; ++i) {
-    webrtc::CodecInst gcodec;
-    if (voe_wrapper_->codec()->GetCodec(i, gcodec) >= 0) {
-      AudioCodec codec(gcodec.pltype, gcodec.plname,
-                       gcodec.plfreq, gcodec.rate, gcodec.channels, 0);
+    webrtc::CodecInst voe_codec;
+    if (voe_wrapper_->codec()->GetCodec(i, voe_codec) >= 0) {
+      AudioCodec codec(voe_codec.pltype, voe_codec.plname, voe_codec.plfreq,
+                       voe_codec.rate, voe_codec.channels, 0);
+      // Allow arbitrary rates for ISAC to be specified.
+      if (_stricmp(codec.name.c_str(), kIsacCodecName) == 0) {
+        codec.bitrate = 0;
+      }
       if (codec.Matches(in)) {
         if (out) {
-          // If the codec is VBR and an explicit rate is specified, use it.
-          if (in.bitrate != 0 && gcodec.rate == -1) {
-            gcodec.rate = in.bitrate;
+          // Fixup the payload type.
+          voe_codec.pltype = in.id;
+          // If ISAC is being used, and an explicit bitrate is not specified,
+          // enable auto bandwidth adjustment.
+          if (_stricmp(codec.name.c_str(), kIsacCodecName) == 0) {
+            voe_codec.rate = (in.bitrate > 0) ? in.bitrate : -1;
           }
-          *out = gcodec;
+          *out = voe_codec;
         }
         return true;
       }
@@ -863,16 +893,6 @@
   }
 }
 
-int WebRtcVoiceEngine::GetCodecPreference(const char *name, int clockrate) {
-  for (size_t i = 0; i < ARRAY_SIZE(kCodecPrefs); ++i) {
-    if ((strcmp(kCodecPrefs[i].name, name) == 0) &&
-        (kCodecPrefs[i].clockrate == clockrate))
-      return ARRAY_SIZE(kCodecPrefs) - i;
-  }
-  LOG(LS_WARNING) << "Unexpected codec \"" << name << "/" << clockrate << "\"";
-  return -1;
-}
-
 bool WebRtcVoiceEngine::FindChannelAndSsrc(
     int channel_num, WebRtcVoiceMediaChannel** channel, uint32* ssrc) const {
   ASSERT(channel != NULL && ssrc != NULL);
@@ -973,6 +993,17 @@
   return true;
 }
 
+bool WebRtcVoiceEngine::SetAudioDeviceModule(webrtc::AudioDeviceModule* adm,
+    webrtc::AudioDeviceModule* adm_sc) {
+  if (initialized_) {
+    LOG(LS_WARNING) << "SetAudioDeviceModule can not be called after Init.";
+    return false;
+  }
+  adm_ = adm;
+  adm_sc_ = adm_sc;
+  return true;
+}
+
 // WebRtcVoiceMediaChannel
 WebRtcVoiceMediaChannel::WebRtcVoiceMediaChannel(WebRtcVoiceEngine *engine)
     : WebRtcMediaChannel<VoiceMediaChannel, WebRtcVoiceEngine>(
@@ -1000,6 +1031,16 @@
 
   // Create a random but nonzero send SSRC
   SetSendSsrc(talk_base::CreateRandomNonZeroId());
+
+  // Reset all recv codecs; they will be enabled via SetRecvCodecs.
+  int ncodecs = engine->voe()->codec()->NumOfCodecs();
+  for (int i = 0; i < ncodecs; ++i) {
+    webrtc::CodecInst voe_codec;
+    if (engine->voe()->codec()->GetCodec(i, voe_codec) >= 0) {
+      voe_codec.pltype = -1;
+      engine->voe()->codec()->SetRecPayloadType(voe_channel(), voe_codec);
+    }
+  }
 }
 
 WebRtcVoiceMediaChannel::~WebRtcVoiceMediaChannel() {
@@ -1042,26 +1083,22 @@
 
 bool WebRtcVoiceMediaChannel::SetRecvCodecs(
     const std::vector<AudioCodec>& codecs) {
-  // Update our receive payload types to match what we offered. This only is
-  // an issue when a different entity (i.e. a server) is generating the offer
-  // for us.
+  // Set the payload types to be used for incoming media.
   bool ret = true;
-  for (std::vector<AudioCodec>::const_iterator i = codecs.begin();
-       i != codecs.end() && ret; ++i) {
-    webrtc::CodecInst gcodec;
-    if (engine()->FindWebRtcCodec(*i, &gcodec)) {
-      if (gcodec.pltype != i->id) {
-        LOG(LS_INFO) << "Updating payload type for " << gcodec.plname
-                  << " from " << gcodec.pltype << " to " << i->id;
-        gcodec.pltype = i->id;
-        if (engine()->voe()->codec()->SetRecPayloadType(
-            voe_channel(), gcodec) == -1) {
-          LOG_RTCERR1(SetRecPayloadType, voe_channel());
-          ret = false;
-        }
+  LOG(LS_INFO) << "Setting receive voice codecs:";
+  for (std::vector<AudioCodec>::const_iterator it = codecs.begin();
+       it != codecs.end() && ret; ++it) {
+    webrtc::CodecInst voe_codec;
+    if (engine()->FindWebRtcCodec(*it, &voe_codec)) {
+      LOG(LS_INFO) << ToString(*it);
+      voe_codec.pltype = it->id;
+      if (engine()->voe()->codec()->SetRecPayloadType(
+          voe_channel(), voe_codec) == -1) {
+        LOG_RTCERR2(SetRecPayloadType, voe_channel(), ToString(voe_codec));
+        ret = false;
       }
     } else {
-      LOG(LS_WARNING) << "Unknown codec " << i->name;
+      LOG(LS_WARNING) << "Unknown codec " << ToString(*it);
       ret = false;
     }
   }
@@ -1082,29 +1119,30 @@
   webrtc::CodecInst send_codec;
   memset(&send_codec, 0, sizeof(send_codec));
 
-  for (std::vector<AudioCodec>::const_iterator i = codecs.begin();
-       i != codecs.end(); ++i) {
+  for (std::vector<AudioCodec>::const_iterator it = codecs.begin();
+       it != codecs.end(); ++it) {
     // Ignore codecs we don't know about. The negotiation step should prevent
     // this, but double-check to be sure.
-    webrtc::CodecInst gcodec;
-    if (!engine()->FindWebRtcCodec(*i, &gcodec)) {
-      LOG(LS_WARNING) << "Unknown codec " << i->name;
+    webrtc::CodecInst voe_codec;
+    if (!engine()->FindWebRtcCodec(*it, &voe_codec)) {
+      LOG(LS_WARNING) << "Unknown codec " << ToString(voe_codec);
       continue;
     }
 
     // Find the DTMF telephone event "codec" and tell VoiceEngine about it.
-    if (i->name == "telephone-event" || i->name == "audio/telephone-event") {
+    if (_stricmp(it->name.c_str(), "telephone-event") == 0 ||
+        _stricmp(it->name.c_str(), "audio/telephone-event") == 0) {
       engine()->voe()->dtmf()->SetSendTelephoneEventPayloadType(
-          voe_channel(), i->id);
+          voe_channel(), it->id);
       dtmf_allowed_ = true;
     }
 
     // Turn voice activity detection/comfort noise on if supported.
     // Set the wideband CN payload type appropriately.
     // (narrowband always uses the static payload type 13).
-    if (i->name == "CN") {
+    if (_stricmp(it->name.c_str(), "CN") == 0) {
       webrtc::PayloadFrequencies cn_freq;
-      switch (i->clockrate) {
+      switch (it->clockrate) {
         case 8000:
           cn_freq = webrtc::kFreq8000Hz;
           break;
@@ -1115,14 +1153,14 @@
           cn_freq = webrtc::kFreq32000Hz;
           break;
         default:
-          LOG(LS_WARNING) << "CN frequency " << i->clockrate
+          LOG(LS_WARNING) << "CN frequency " << it->clockrate
                           << " not supported.";
           continue;
       }
       engine()->voe()->codec()->SetVADStatus(voe_channel(), true);
       if (cn_freq != webrtc::kFreq8000Hz) {
         engine()->voe()->codec()->SetSendCNPayloadType(voe_channel(),
-                                                       i->id, cn_freq);
+                                                       it->id, cn_freq);
       }
     }
 
@@ -1131,24 +1169,23 @@
     // "red", for FEC audio, is a special case where the actual codec to be
     // used is specified in params.
     if (first) {
-      if (i->name == "red") {
+      if (_stricmp(it->name.c_str(), "red") == 0) {
         // Parse out the RED parameters. If we fail, just ignore RED;
         // we don't support all possible params/usage scenarios.
-        if (!GetRedSendCodec(*i, codecs, &send_codec)) {
+        if (!GetRedSendCodec(*it, codecs, &send_codec)) {
           continue;
         }
 
         // Enable redundant encoding of the specified codec. Treat any
         // failure as a fatal internal error.
-        LOG(LS_INFO) << "Enabling RED";
+        LOG(LS_INFO) << "Enabling FEC";
         if (engine()->voe()->rtp()->SetFECStatus(voe_channel(),
-                                                    true, i->id) == -1) {
-          LOG_RTCERR3(SetFECStatus, voe_channel(), true, i->id);
+                                                 true, it->id) == -1) {
+          LOG_RTCERR3(SetFECStatus, voe_channel(), true, it->id);
           return false;
         }
       } else {
-        send_codec = gcodec;
-        send_codec.pltype = i->id;
+        send_codec = voe_codec;
       }
       first = false;
     }
@@ -1163,11 +1200,11 @@
   }
 
   // Set the codec.
-  LOG(LS_INFO) << "Selected voice codec " << send_codec.plname
-            << "/" << send_codec.plfreq;
+  LOG(LS_INFO) << "Selected voice codec " << ToString(send_codec)
+               << ", bitrate=" << send_codec.rate;
   if (engine()->voe()->codec()->SetSendCodec(voe_channel(),
-                                                send_codec) == -1) {
-    LOG_RTCERR1(SetSendCodec, voe_channel());
+                                             send_codec) == -1) {
+    LOG_RTCERR2(SetSendCodec, voe_channel(), ToString(send_codec));
     return false;
   }
 
@@ -1893,7 +1930,6 @@
   // SetSendCodec, with the desired payload type.
   if (codec != all_codecs.end() &&
     engine()->FindWebRtcCodec(*codec, send_codec)) {
-    send_codec->pltype = red_pt;
   } else {
     LOG(LS_WARNING) << "RED params " << red_params << " are invalid.";
     return false;
diff --git a/talk/session/phone/webrtcvoiceengine.h b/talk/session/phone/webrtcvoiceengine.h
index 8234b9b..2f2ab13 100644
--- a/talk/session/phone/webrtcvoiceengine.h
+++ b/talk/session/phone/webrtcvoiceengine.h
@@ -41,6 +41,11 @@
 #include "talk/session/phone/channel.h"
 #include "talk/session/phone/rtputils.h"
 #include "talk/session/phone/webrtccommon.h"
+#ifdef WEBRTC_RELATIVE_PATH
+#include "voice_engine/main/interface/voe_base.h"
+#else
+#include "third_party/webrtc/files/include/voe_base.h"
+#endif  // WEBRTC_RELATIVE_PATH
 
 namespace cricket {
 
@@ -81,8 +86,6 @@
       public webrtc::TraceCallback {
  public:
   WebRtcVoiceEngine();
-  WebRtcVoiceEngine(webrtc::AudioDeviceModule* adm,
-                    webrtc::AudioDeviceModule* adm_sc);
   // Dependency injection for testing.
   WebRtcVoiceEngine(VoEWrapper* voe_wrapper,
                     VoEWrapper* voe_wrapper_sc,
@@ -131,6 +134,10 @@
   VoEWrapper* voe_sc() { return voe_wrapper_sc_.get(); }
   int GetLastEngineError();
 
+  // Set the external ADMs. This can only be called before Init.
+  bool SetAudioDeviceModule(webrtc::AudioDeviceModule* adm,
+                            webrtc::AudioDeviceModule* adm_sc);
+
  private:
   typedef std::vector<WebRtcSoundclipMedia *> SoundclipList;
   typedef std::vector<WebRtcVoiceMediaChannel *> ChannelList;
@@ -138,15 +145,16 @@
   struct CodecPref {
     const char* name;
     int clockrate;
+    int payload_type;
   };
 
   void Construct();
+  void ConstructCodecs();
   bool InitInternal();
   void ApplyLogging(const std::string& log_filter);
   virtual void Print(const webrtc::TraceLevel level,
                      const char* trace_string, const int length);
   virtual void CallbackOnError(const int channel, const int errCode);
-  static int GetCodecPreference(const char *name, int clockrate);
   // Given the device type, name, and id, find device id. Return true and
   // set the output parameter rtc_id if successful.
   bool FindWebRtcAudioDeviceId(
diff --git a/talk/session/phone/webrtcvoiceengine_unittest.cc b/talk/session/phone/webrtcvoiceengine_unittest.cc
index 0660630..768f1fd 100644
--- a/talk/session/phone/webrtcvoiceengine_unittest.cc
+++ b/talk/session/phone/webrtcvoiceengine_unittest.cc
@@ -14,7 +14,7 @@
 // Tests for the WebRtcVoiceEngine/VoiceChannel code.
 
 static const cricket::AudioCodec kPcmuCodec(0, "PCMU", 8000, 64000, 1, 0);
-static const cricket::AudioCodec kIsacCodec(103, "ISAC", 16000, -1, 1, 0);
+static const cricket::AudioCodec kIsacCodec(103, "ISAC", 16000, 32000, 1, 0);
 static const cricket::AudioCodec kRedCodec(117, "red", 8000, 0, 1, 0);
 static const cricket::AudioCodec kCn8000Codec(13, "CN", 8000, 0, 1, 0);
 static const cricket::AudioCodec kCn16000Codec(105, "CN", 16000, 0, 1, 0);
@@ -87,7 +87,7 @@
     uint32 ssrc_;
     cricket::VoiceMediaChannel::Error error_;
   };
-  // TODO: Implement other stub interfaces (VQE)
+
   WebRtcVoiceEngineTest()
       : voe_(kAudioCodecs, ARRAY_SIZE(kAudioCodecs)),
         voe_sc_(kAudioCodecs, ARRAY_SIZE(kAudioCodecs)),
@@ -149,6 +149,21 @@
   EXPECT_TRUE(channel_ == NULL);
 }
 
+// Tests that the list of supported codecs is created properly and ordered
+// correctly
+TEST_F(WebRtcVoiceEngineTest, CodecPreference) {
+  const std::vector<cricket::AudioCodec>& codecs = engine_.codecs();
+  ASSERT_FALSE(codecs.empty());
+  EXPECT_EQ("ISAC", codecs[0].name);
+  EXPECT_EQ(16000, codecs[0].clockrate);
+  EXPECT_EQ(0, codecs[0].bitrate);
+  int pref = codecs[0].preference;
+  for (size_t i = 1; i < codecs.size(); ++i) {
+    EXPECT_GT(pref, codecs[i].preference);
+    pref = codecs[i].preference;
+  }
+}
+
 // Tests that we can find codecs by name or id, and that we interpret the
 // clockrate and bitrate fields properly.
 TEST_F(WebRtcVoiceEngineTest, FindCodec) {
@@ -164,20 +179,24 @@
   codec = kIsacCodec;
   codec.id = 127;
   EXPECT_TRUE(engine_.FindWebRtcCodec(codec, &codec_inst));
+  EXPECT_EQ(codec.id, codec_inst.pltype);
   // Find PCMU with a 0 clockrate.
   codec = kPcmuCodec;
   codec.clockrate = 0;
   EXPECT_TRUE(engine_.FindWebRtcCodec(codec, &codec_inst));
+  EXPECT_EQ(codec.id, codec_inst.pltype);
   EXPECT_EQ(8000, codec_inst.plfreq);
   // Find PCMU with a 0 bitrate.
   codec = kPcmuCodec;
   codec.bitrate = 0;
   EXPECT_TRUE(engine_.FindWebRtcCodec(codec, &codec_inst));
+  EXPECT_EQ(codec.id, codec_inst.pltype);
   EXPECT_EQ(64000, codec_inst.rate);
   // Find ISAC with an explicit bitrate.
   codec = kIsacCodec;
   codec.bitrate = 32000;
   EXPECT_TRUE(engine_.FindWebRtcCodec(codec, &codec_inst));
+  EXPECT_EQ(codec.id, codec_inst.pltype);
   EXPECT_EQ(32000, codec_inst.rate);
 }
 
@@ -188,14 +207,22 @@
   std::vector<cricket::AudioCodec> codecs;
   codecs.push_back(kIsacCodec);
   codecs.push_back(kPcmuCodec);
-  codecs[0].id = 96;
+  codecs.push_back(kTelephoneEventCodec);
+  codecs[0].id = 106;  // collide with existing telephone-event
+  codecs[2].id = 126;
   EXPECT_TRUE(channel_->SetRecvCodecs(codecs));
   webrtc::CodecInst gcodec;
   talk_base::strcpyn(gcodec.plname, ARRAY_SIZE(gcodec.plname), "ISAC");
   gcodec.plfreq = 16000;
   EXPECT_EQ(0, voe_.GetRecPayloadType(channel_num, gcodec));
-  EXPECT_EQ(96, gcodec.pltype);
+  EXPECT_EQ(106, gcodec.pltype);
   EXPECT_STREQ("ISAC", gcodec.plname);
+  talk_base::strcpyn(gcodec.plname, ARRAY_SIZE(gcodec.plname),
+      "telephone-event");
+  gcodec.plfreq = 8000;
+  EXPECT_EQ(0, voe_.GetRecPayloadType(channel_num, gcodec));
+  EXPECT_EQ(126, gcodec.pltype);
+  EXPECT_STREQ("telephone-event", gcodec.plname);
 }
 
 // Test that we fail to set an unknown inbound codec.
@@ -207,6 +234,16 @@
   EXPECT_FALSE(channel_->SetRecvCodecs(codecs));
 }
 
+// Test that we fail if we have duplicate types in the inbound list.
+TEST_F(WebRtcVoiceEngineTest, SetRecvCodecsDuplicatePayloadType) {
+  EXPECT_TRUE(SetupEngine());
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kIsacCodec);
+  codecs.push_back(kCn16000Codec);
+  codecs[1].id = kIsacCodec.id;
+  EXPECT_FALSE(channel_->SetRecvCodecs(codecs));
+}
+
 // Test that we apply codecs properly.
 TEST_F(WebRtcVoiceEngineTest, SetSendCodecs) {
   EXPECT_TRUE(SetupEngine());
@@ -228,6 +265,44 @@
   EXPECT_EQ(106, voe_.GetSendTelephoneEventPayloadType(channel_num));
 }
 
+// Test that we handle various ways of specifying bitrate.
+TEST_F(WebRtcVoiceEngineTest, SetSendCodecsBitrate) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kIsacCodec);  // bitrate == 32000
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  webrtc::CodecInst gcodec;
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(103, gcodec.pltype);
+  EXPECT_STREQ("ISAC", gcodec.plname);
+  EXPECT_EQ(32000, gcodec.rate);
+  codecs[0].bitrate = 0;         // bitrate == default
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(103, gcodec.pltype);
+  EXPECT_STREQ("ISAC", gcodec.plname);
+  EXPECT_EQ(-1, gcodec.rate);
+  codecs[0].bitrate = 28000;     // bitrate == 28000
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(103, gcodec.pltype);
+  EXPECT_STREQ("ISAC", gcodec.plname);
+  EXPECT_EQ(28000, gcodec.rate);
+  codecs[0] = kPcmuCodec;        // bitrate == 64000
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(0, gcodec.pltype);
+  EXPECT_STREQ("PCMU", gcodec.plname);
+  EXPECT_EQ(64000, gcodec.rate);
+  codecs[0].bitrate = 0;         // bitrate == default
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(0, gcodec.pltype);
+  EXPECT_STREQ("PCMU", gcodec.plname);
+  EXPECT_EQ(64000, gcodec.rate);
+}
+
 // Test that we fall back to PCMU if no codecs are specified.
 TEST_F(WebRtcVoiceEngineTest, SetSendCodecsNoCodecs) {
   EXPECT_TRUE(SetupEngine());
@@ -272,6 +347,33 @@
   EXPECT_EQ(98, voe_.GetSendTelephoneEventPayloadType(channel_num));
 }
 
+// Test that we perform case-insensitive matching of codec names.
+TEST_F(WebRtcVoiceEngineTest, SetSendCodecsCaseInsensitive) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kIsacCodec);
+  codecs.push_back(kPcmuCodec);
+  codecs.push_back(kCn16000Codec);
+  codecs.push_back(kCn8000Codec);
+  codecs.push_back(kTelephoneEventCodec);
+  codecs.push_back(kRedCodec);
+  codecs[0].name = "iSaC";
+  codecs[0].id = 96;
+  codecs[2].id = 97;  // wideband CN
+  codecs[4].id = 98;  // DTMF
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  webrtc::CodecInst gcodec;
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(96, gcodec.pltype);
+  EXPECT_STREQ("ISAC", gcodec.plname);
+  EXPECT_TRUE(voe_.GetVAD(channel_num));
+  EXPECT_FALSE(voe_.GetFEC(channel_num));
+  EXPECT_EQ(13, voe_.GetSendCNPayloadType(channel_num, false));
+  EXPECT_EQ(97, voe_.GetSendCNPayloadType(channel_num, true));
+  EXPECT_EQ(98, voe_.GetSendTelephoneEventPayloadType(channel_num));
+}
+
 // Test that we set up FEC correctly.
 TEST_F(WebRtcVoiceEngineTest, SetSendCodecsRED) {
   EXPECT_TRUE(SetupEngine());
@@ -1014,21 +1116,21 @@
       cricket::AudioCodec(96, "ISAC", 16000, 0, 1, 0)));
   EXPECT_TRUE(engine.FindCodec(
       cricket::AudioCodec(96, "ISAC", 32000, 0, 1, 0)));
+  // Check that name matching is case-insensitive.
+  EXPECT_TRUE(engine.FindCodec(
+      cricket::AudioCodec(96, "ILBC", 8000, 0, 1, 0)));
   EXPECT_TRUE(engine.FindCodec(
       cricket::AudioCodec(96, "iLBC", 8000, 0, 1, 0)));
   EXPECT_TRUE(engine.FindCodec(
       cricket::AudioCodec(96, "PCMU", 8000, 0, 1, 0)));
   EXPECT_TRUE(engine.FindCodec(
       cricket::AudioCodec(96, "PCMA", 8000, 0, 1, 0)));
-// TODO: Add speex back, once webrtc supports it.
-//  EXPECT_TRUE(engine.FindCodec(
-//      cricket::AudioCodec(96, "speex", 16000, 0, 1, 0)));
-//  EXPECT_TRUE(engine.FindCodec(
-//      cricket::AudioCodec(96, "speex", 8000, 0, 1, 0)));
+  EXPECT_TRUE(engine.FindCodec(
+      cricket::AudioCodec(96, "speex", 16000, 0, 1, 0)));
+  EXPECT_TRUE(engine.FindCodec(
+      cricket::AudioCodec(96, "speex", 8000, 0, 1, 0)));
   EXPECT_TRUE(engine.FindCodec(
       cricket::AudioCodec(96, "G722", 16000, 0, 1, 0)));
-//  EXPECT_TRUE(engine.FindCodec(
-//      cricket::AudioCodec(96, "GSM", 8000, 0, 1, 0)));
   EXPECT_TRUE(engine.FindCodec(
       cricket::AudioCodec(96, "red", 8000, 0, 1, 0)));
   EXPECT_TRUE(engine.FindCodec(
@@ -1045,7 +1147,7 @@
   EXPECT_TRUE(engine.FindCodec(
       cricket::AudioCodec(8, "", 8000, 0, 1, 0)));   // PCMA
   EXPECT_TRUE(engine.FindCodec(
-      cricket::AudioCodec(9, "", 16000, 0, 1, 0)));   // G722
+      cricket::AudioCodec(9, "", 16000, 0, 1, 0)));  // G722
   EXPECT_TRUE(engine.FindCodec(
       cricket::AudioCodec(13, "", 8000, 0, 1, 0)));  // CN
   // Check sample/bitrate matching.
@@ -1058,43 +1160,31 @@
   EXPECT_FALSE(engine.FindCodec(cricket::AudioCodec(0, "", 5000, 0, 1, 0)));
   EXPECT_FALSE(engine.FindCodec(cricket::AudioCodec(0, "", 0, 5000, 1, 0)));
   // Check that there aren't any extra codecs lying around.
-  EXPECT_EQ(11U, engine.codecs().size());
+  EXPECT_EQ(13U, engine.codecs().size());
   // Verify the payload id of common audio codecs, including CN, ISAC, and G722.
-  // TODO: WebRtc team may change the payload id.
   for (std::vector<cricket::AudioCodec>::const_iterator it =
       engine.codecs().begin(); it != engine.codecs().end(); ++it) {
     if (it->name == "CN" && it->clockrate == 16000) {
-      EXPECT_EQ(98, it->id);
+      EXPECT_EQ(105, it->id);
     } else if (it->name == "CN" && it->clockrate == 32000) {
-      EXPECT_EQ(99, it->id);
+      EXPECT_EQ(106, it->id);
     } else if (it->name == "ISAC" && it->clockrate == 16000) {
       EXPECT_EQ(103, it->id);
     } else if (it->name == "ISAC" && it->clockrate == 32000) {
       EXPECT_EQ(104, it->id);
     } else if (it->name == "G722" && it->clockrate == 16000) {
       EXPECT_EQ(9, it->id);
+    } else if (it->name == "telephone-event") {
+      EXPECT_EQ(126, it->id);
+    } else if (it->name == "red") {
+      EXPECT_EQ(127, it->id);
     }
   }
 
   engine.Terminate();
 }
 
-// Tests that the list of supported codecs is created properly and ordered
-// correctly
-TEST_F(WebRtcVoiceEngineTest, CodecPreference) {
-  cricket::WebRtcVoiceEngine engine;
-  const std::vector<cricket::AudioCodec>& codecs = engine.codecs();
-  ASSERT_FALSE(codecs.empty());
-  EXPECT_EQ("ISAC", codecs[0].name);
-  EXPECT_EQ(16000, codecs[0].clockrate);
-  EXPECT_EQ(32000, codecs[0].bitrate);
-  int pref = codecs[0].preference;
-  for (size_t i = 1; i < codecs.size(); ++i) {
-    EXPECT_GT(pref, codecs[i].preference);
-    pref = codecs[i].preference;
-  }
-}
-
+// Tests that VoE supports at least 32 channels
 TEST(WebRtcVoiceEngineLibTest, Has32Channels) {
   cricket::WebRtcVoiceEngine engine;
   EXPECT_TRUE(engine.Init());
diff --git a/talk/xmpp/constants.cc b/talk/xmpp/constants.cc
index cf63e79..1003f39 100644
--- a/talk/xmpp/constants.cc
+++ b/talk/xmpp/constants.cc
@@ -367,6 +367,9 @@
 const QName QN_PUBSUB(true, NS_PUBSUB, "pubsub");
 const QName QN_PUBSUB_ITEMS(true, NS_PUBSUB, "items");
 const QName QN_PUBSUB_ITEM(true, NS_PUBSUB, "item");
+const QName QN_PUBSUB_PUBLISH(true, NS_PUBSUB, "publish");
+const QName QN_PUBSUB_RETRACT(true, NS_PUBSUB, "retract");
+const QName QN_ATTR_PUBLISHER(true, STR_EMPTY, "publisher");
 
 const std::string NS_PUBSUB_EVENT("http://jabber.org/protocol/pubsub#event");
 const QName QN_NODE(true, STR_EMPTY, "node");
@@ -374,9 +377,19 @@
 const QName QN_PUBSUB_EVENT_ITEMS(true, NS_PUBSUB_EVENT, "items");
 const QName QN_PUBSUB_EVENT_ITEM(true, NS_PUBSUB_EVENT, "item");
 const QName QN_PUBSUB_EVENT_RETRACT(true, NS_PUBSUB_EVENT, "retract");
+const QName QN_NOTIFY(true, STR_EMPTY, "notify");
 
 
 
+const std::string NS_PRESENTER("google:presenter");
+const QName QN_PRESENTER_PRESENTER(true, NS_PRESENTER, "presenter");
+const QName QN_PRESENTER_PRESENTATION_ITEM(
+    true, NS_PRESENTER, "presentation-item");
+const QName QN_PRESENTER_PRESENTATION_TYPE(
+    true, NS_PRESENTER, "presentation-type");
+const QName QN_PRESENTER_PRESENTATION_ID(true, NS_PRESENTER, "presentation-id");
+
+
 
 // JEP 0030
 const QName QN_CATEGORY(true, STR_EMPTY, "category");
@@ -567,4 +580,13 @@
 const QName QN_GOOGLE_MUC_USER_STATUS(true, NS_GOOGLE_MUC_USER, "status");
 const QName QN_LABEL(true, STR_EMPTY, "label");
 
+const std::string NS_GOOGLE_MUC_MEDIA("google:muc#media");
+const QName QN_GOOGLE_MUC_AUDIO_MUTE(
+    true, NS_GOOGLE_MUC_MEDIA, "audio-mute");
+const QName QN_GOOGLE_MUC_VIDEO_MUTE(
+    true, NS_GOOGLE_MUC_MEDIA, "video-mute");
+const QName QN_GOOGLE_MUC_RECORDING(
+    true, NS_GOOGLE_MUC_MEDIA, "recording");
+const QName QN_STATE_ATTR(true, STR_EMPTY, "state");
+
 }
diff --git a/talk/xmpp/constants.h b/talk/xmpp/constants.h
index ba9c9e1..4409283 100644
--- a/talk/xmpp/constants.h
+++ b/talk/xmpp/constants.h
@@ -331,6 +331,9 @@
 extern const QName QN_PUBSUB;
 extern const QName QN_PUBSUB_ITEMS;
 extern const QName QN_PUBSUB_ITEM;
+extern const QName QN_PUBSUB_PUBLISH;
+extern const QName QN_PUBSUB_RETRACT;
+extern const QName QN_ATTR_PUBLISHER;
 
 extern const std::string NS_PUBSUB_EVENT;
 extern const QName QN_NODE;
@@ -338,6 +341,14 @@
 extern const QName QN_PUBSUB_EVENT_ITEMS;
 extern const QName QN_PUBSUB_EVENT_ITEM;
 extern const QName QN_PUBSUB_EVENT_RETRACT;
+extern const QName QN_NOTIFY;
+
+
+extern const std::string NS_PRESENTER;
+extern const QName QN_PRESENTER_PRESENTER;
+extern const QName QN_PRESENTER_PRESENTATION_ITEM;
+extern const QName QN_PRESENTER_PRESENTATION_TYPE;
+extern const QName QN_PRESENTER_PRESENTATION_ID;
 
 
 
@@ -510,6 +521,12 @@
 extern const QName QN_GOOGLE_MUC_USER_STATUS;
 extern const QName QN_LABEL;
 
+extern const std::string NS_GOOGLE_MUC_MEDIA;
+extern const QName QN_GOOGLE_MUC_AUDIO_MUTE;
+extern const QName QN_GOOGLE_MUC_VIDEO_MUTE;
+extern const QName QN_GOOGLE_MUC_RECORDING;
+extern const QName QN_STATE_ATTR;
+
 }  // namespace buzz
 
 #endif  // TALK_XMPP_CONSTANTS_H_
diff --git a/talk/xmpp/hangoutpubsubclient.cc b/talk/xmpp/hangoutpubsubclient.cc
new file mode 100644
index 0000000..ea5534d
--- /dev/null
+++ b/talk/xmpp/hangoutpubsubclient.cc
@@ -0,0 +1,466 @@
+/*
+ * 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/xmpp/hangoutpubsubclient.h"
+
+#include "talk/base/logging.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/jid.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlelement.h"
+
+
+// Gives a high-level API for MUC call PubSub needs such as
+// presenter state, recording state, mute state, and remote mute.
+
+namespace buzz {
+
+namespace {
+const std::string kPresenting = "s";
+const std::string kNotPresenting = "o";
+}  // namespace
+
+
+// Knows how to handle specific states and XML.
+template <typename C>
+class PubSubStateSerializer {
+ public:
+  virtual XmlElement* Write(const QName& state_name, const C& state) = 0;
+  virtual C Parse(const XmlElement* state_elem) = 0;
+};
+
+// A simple serialiazer where presence of item => true, lack of item
+// => false.
+class BoolStateSerializer : public PubSubStateSerializer<bool> {
+  virtual XmlElement* Write(const QName& state_name, const bool& state) {
+    if (!state) {
+      return NULL;
+    }
+
+    return new XmlElement(state_name, true);
+  }
+
+  virtual bool Parse(const XmlElement* state_elem) {
+    return state_elem != NULL;
+  }
+};
+
+// Adapts PubSubClient to be specifically suited for pub sub call
+// states.  Signals state changes and keeps track of keys, which are
+// normally nicks.
+// TODO: Expose this as a generally useful class, not just
+// private to hangouts.
+template <typename C>
+class PubSubStateClient : public sigslot::has_slots<> {
+ public:
+  // Gets ownership of the serializer, but not the client.
+  PubSubStateClient(PubSubClient* client,
+                    const QName& state_name,
+                    C default_state,
+                    PubSubStateSerializer<C>* serializer)
+      : client_(client),
+        state_name_(state_name),
+        default_state_(default_state) {
+    serializer_.reset(serializer);
+    client_->SignalItems.connect(
+        this, &PubSubStateClient<C>::OnItems);
+    client_->SignalPublishResult.connect(
+        this, &PubSubStateClient<C>::OnPublishResult);
+    client_->SignalPublishError.connect(
+        this, &PubSubStateClient<C>::OnPublishError);
+    client_->SignalRetractResult.connect(
+        this, &PubSubStateClient<C>::OnRetractResult);
+    client_->SignalRetractError.connect(
+        this, &PubSubStateClient<C>::OnRetractError);
+  }
+
+  virtual ~PubSubStateClient() {}
+
+  virtual void Publish(const std::string& key, const C& state,
+                       std::string* task_id_out) {
+    const std::string& nick = key;
+
+    std::string itemid = state_name_.LocalPart() + ":" + nick;
+    if (StatesEqual(state, default_state_)) {
+      client_->RetractItem(itemid, task_id_out);
+    } else {
+      XmlElement* state_elem = serializer_->Write(state_name_, state);
+      state_elem->AddAttr(QN_NICK, nick);
+      client_->PublishItem(itemid, state_elem, task_id_out);
+    }
+  };
+
+  sigslot::signal1<const PubSubStateChange<C>&> SignalStateChange;
+  // Signal (task_id, item).  item is NULL for retract.
+  sigslot::signal2<const std::string&,
+                   const XmlElement*> SignalPublishResult;
+  // Signal (task_id, item, error stanza).  item is NULL for retract.
+  sigslot::signal3<const std::string&,
+                   const XmlElement*,
+                   const XmlElement*> SignalPublishError;
+
+ protected:
+  // return false if retracted item (no state given)
+  virtual bool ParseState(const PubSubItem& item,
+                          std::string* key_out,
+                          bool* state_out) {
+    const XmlElement* state_elem = item.elem->FirstNamed(state_name_);
+    if (state_elem == NULL) {
+      return false;
+    }
+
+    *key_out = state_elem->Attr(QN_NICK);
+    *state_out = serializer_->Parse(state_elem);
+    return true;
+  };
+
+  virtual bool StatesEqual(C state1, C state2) {
+    return state1 == state2;
+  }
+
+  PubSubClient* client() { return client_; }
+
+ private:
+  void OnItems(PubSubClient* pub_sub_client,
+               const std::vector<PubSubItem>& items) {
+    for (std::vector<PubSubItem>::const_iterator item = items.begin();
+         item != items.end(); ++item) {
+      OnItem(*item);
+    }
+  }
+
+  void OnItem(const PubSubItem& item) {
+    const std::string& itemid = item.itemid;
+
+    std::string key;
+    C new_state;
+    bool retracted = !ParseState(item, &key, &new_state);
+    if (retracted) {
+      bool known_itemid = (key_by_itemid_.find(itemid) != key_by_itemid_.end());
+      if (!known_itemid) {
+        // Nothing to retract, and nothing to publish.
+        // Probably a different state type.
+        return;
+      } else {
+        key = key_by_itemid_[itemid];
+        key_by_itemid_.erase(itemid);
+        new_state = default_state_;
+      }
+    } else {
+      // TODO: Assert parsed key matches the known key when
+      // not retracted.  It shouldn't change!
+      key_by_itemid_[itemid] = key;
+    }
+
+    bool has_old_state = (state_by_key_.find(key) != state_by_key_.end());
+    const C& old_state = has_old_state ? state_by_key_[key] : default_state_;
+    if ((retracted && !has_old_state) || StatesEqual(new_state, old_state)) {
+      // Nothing change, so don't bother signalling.
+      return;
+    }
+
+    if (retracted || StatesEqual(new_state, default_state_)) {
+      // We treat a default state similar to a retract.
+      state_by_key_.erase(key);
+    } else {
+      state_by_key_[key] = new_state;
+    }
+
+    PubSubStateChange<C> change;
+    change.key = key;
+    change.old_state = old_state;
+    change.new_state = new_state;
+    change.publisher_nick =
+        Jid(item.elem->Attr(QN_ATTR_PUBLISHER)).resource();
+    SignalStateChange(change);
+ }
+
+  void OnPublishResult(PubSubClient* pub_sub_client,
+                       const std::string& task_id,
+                       const XmlElement* item) {
+    SignalPublishResult(task_id, item);
+  }
+
+  void OnPublishError(PubSubClient* pub_sub_client,
+                      const std::string& task_id,
+                      const buzz::XmlElement* item,
+                      const buzz::XmlElement* stanza) {
+    SignalPublishError(task_id, item, stanza);
+  }
+
+  void OnRetractResult(PubSubClient* pub_sub_client,
+                       const std::string& task_id) {
+    // There's no point in differentiating between publish and retract
+    // errors, so we simplify by making them both signal a publish
+    // result.
+    const XmlElement* item = NULL;
+    SignalPublishResult(task_id, item);
+  }
+
+  void OnRetractError(PubSubClient* pub_sub_client,
+                      const std::string& task_id,
+                      const buzz::XmlElement* stanza) {
+    // There's no point in differentiating between publish and retract
+    // errors, so we simplify by making them both signal a publish
+    // error.
+    const XmlElement* item = NULL;
+    SignalPublishError(task_id, item, stanza);
+  }
+
+  PubSubClient* client_;
+  const QName& state_name_;
+  C default_state_;
+  talk_base::scoped_ptr<PubSubStateSerializer<C> > serializer_;
+  // key => state
+  std::map<std::string, C> state_by_key_;
+  // itemid => key
+  std::map<std::string, std::string> key_by_itemid_;
+};
+
+class PresenterStateClient : public PubSubStateClient<bool> {
+ public:
+  PresenterStateClient(PubSubClient* client,
+                       const QName& state_name,
+                       bool default_state,
+                       PubSubStateSerializer<bool>* serializer = NULL)
+      : PubSubStateClient<bool>(client, state_name, default_state, serializer) {
+  }
+
+  virtual void Publish(const std::string& key, const bool& state,
+                       std::string* task_id_out) {
+    const std::string& nick = key;
+
+    XmlElement* presenter_elem = new XmlElement(QN_PRESENTER_PRESENTER, true);
+    // There's a dummy value, not used, but required.
+    presenter_elem->AddAttr(QN_JID, "dummy@value.net");
+    presenter_elem->AddAttr(QN_NICK, nick);
+
+    XmlElement* presentation_item_elem =
+        new XmlElement(QN_PRESENTER_PRESENTATION_ITEM, false);
+    const std::string& presentation_type = state ? kPresenting : kNotPresenting;
+    presentation_item_elem->AddAttr(
+        QN_PRESENTER_PRESENTATION_TYPE, presentation_type);
+
+    // The Presenter state is kind of dumb in that it doesn't use
+    // retracts.  It relies on setting the "type" to a special value.
+    std::string itemid = nick;
+    std::vector<XmlElement*> children;
+    children.push_back(presenter_elem);
+    children.push_back(presentation_item_elem);
+    client()->PublishItem(itemid, children, task_id_out);
+  }
+
+ protected:
+  virtual bool ParseState(const PubSubItem& item,
+                          std::string* key_out,
+                          bool* state_out) {
+    const XmlElement* presenter_elem =
+        item.elem->FirstNamed(QN_PRESENTER_PRESENTER);
+    const XmlElement* presentation_item_elem =
+        item.elem->FirstNamed(QN_PRESENTER_PRESENTATION_ITEM);
+    if (presentation_item_elem == NULL || presenter_elem == NULL) {
+      return false;
+    }
+
+    *key_out = presenter_elem->Attr(QN_NICK);
+    *state_out = (presentation_item_elem->Attr(
+        QN_PRESENTER_PRESENTATION_TYPE) != kNotPresenting);
+    return true;
+  }
+};
+
+HangoutPubSubClient::HangoutPubSubClient(XmppTaskParentInterface* parent,
+                                         const Jid& mucjid,
+                                         const std::string& nick)
+    : mucjid_(mucjid),
+      nick_(nick) {
+  presenter_client_.reset(new PubSubClient(parent, mucjid, NS_PRESENTER));
+  presenter_client_->SignalRequestError.connect(
+      this, &HangoutPubSubClient::OnPresenterRequestError);
+
+  media_client_.reset(new PubSubClient(parent, mucjid, NS_GOOGLE_MUC_MEDIA));
+  media_client_->SignalRequestError.connect(
+      this, &HangoutPubSubClient::OnMediaRequestError);
+
+  presenter_state_client_.reset(new PresenterStateClient(
+      presenter_client_.get(), QN_PRESENTER_PRESENTER, false));
+  presenter_state_client_->SignalStateChange.connect(
+      this, &HangoutPubSubClient::OnPresenterStateChange);
+  presenter_state_client_->SignalPublishResult.connect(
+      this, &HangoutPubSubClient::OnPresenterPublishResult);
+  presenter_state_client_->SignalPublishError.connect(
+      this, &HangoutPubSubClient::OnPresenterPublishError);
+
+  audio_mute_state_client_.reset(new PubSubStateClient<bool>(
+      media_client_.get(), QN_GOOGLE_MUC_AUDIO_MUTE, false,
+      new BoolStateSerializer()));
+  // Can't just repeat because we need to watch for remote mutes.
+  audio_mute_state_client_->SignalStateChange.connect(
+      this, &HangoutPubSubClient::OnAudioMuteStateChange);
+  audio_mute_state_client_->SignalPublishResult.connect(
+      this, &HangoutPubSubClient::OnAudioMutePublishResult);
+  audio_mute_state_client_->SignalPublishError.connect(
+      this, &HangoutPubSubClient::OnAudioMutePublishError);
+
+  recording_state_client_.reset(new PubSubStateClient<bool>(
+      media_client_.get(), QN_GOOGLE_MUC_RECORDING, false,
+      new BoolStateSerializer()));
+  recording_state_client_->SignalStateChange.connect(
+      this, &HangoutPubSubClient::OnRecordingStateChange);
+  recording_state_client_->SignalPublishResult.connect(
+      this, &HangoutPubSubClient::OnRecordingPublishResult);
+  recording_state_client_->SignalPublishError.connect(
+      this, &HangoutPubSubClient::OnRecordingPublishError);
+}
+
+HangoutPubSubClient::~HangoutPubSubClient() {
+}
+
+void HangoutPubSubClient::RequestAll() {
+  presenter_client_->RequestItems();
+  media_client_->RequestItems();
+}
+
+void HangoutPubSubClient::OnPresenterRequestError(
+    PubSubClient* client, const XmlElement* stanza) {
+  SignalRequestError(client->node(), stanza);
+}
+
+void HangoutPubSubClient::OnMediaRequestError(
+    PubSubClient* client, const XmlElement* stanza) {
+  SignalRequestError(client->node(), stanza);
+}
+
+void HangoutPubSubClient::PublishPresenterState(
+    bool presenting, std::string* task_id_out) {
+  presenter_state_client_->Publish(nick_, presenting, task_id_out);
+}
+
+void HangoutPubSubClient::PublishAudioMuteState(
+    bool muted, std::string* task_id_out) {
+  audio_mute_state_client_->Publish(nick_, muted, task_id_out);
+}
+
+void HangoutPubSubClient::PublishRecordingState(
+    bool recording, std::string* task_id_out) {
+  recording_state_client_->Publish(nick_, recording, task_id_out);
+}
+
+// Remote mute is accomplished by setting another client's mute state.
+void HangoutPubSubClient::RemoteMute(
+    const std::string& mutee_nick, std::string* task_id_out) {
+  audio_mute_state_client_->Publish(mutee_nick, true, task_id_out);
+}
+
+void HangoutPubSubClient::OnPresenterStateChange(
+    const PubSubStateChange<bool>& change) {
+  const std::string& nick = change.key;
+  SignalPresenterStateChange(nick, change.old_state, change.new_state);
+}
+
+void HangoutPubSubClient::OnPresenterPublishResult(
+    const std::string& task_id, const XmlElement* item) {
+  SignalPublishPresenterResult(task_id);
+}
+
+void HangoutPubSubClient::OnPresenterPublishError(
+    const std::string& task_id, const XmlElement* item,
+    const XmlElement* stanza) {
+  SignalPublishPresenterError(task_id, stanza);
+}
+
+// Since a remote mute is accomplished by another client setting our
+// mute state, if our state changes to muted, we should mute
+// ourselves.  Note that we never remote un-mute, though.
+void HangoutPubSubClient::OnAudioMuteStateChange(
+    const PubSubStateChange<bool>& change) {
+  const std::string& nick = change.key;
+
+  bool was_muted = change.old_state;
+  bool is_muted = change.new_state;
+  bool remote_action =
+      !change.publisher_nick.empty() && (change.publisher_nick != nick);
+  if (is_muted && remote_action) {
+    const std::string& mutee_nick = nick;
+    const std::string& muter_nick = change.publisher_nick;
+    bool should_mute_locally = (mutee_nick == nick_);
+    SignalRemoteMute(mutee_nick, muter_nick, should_mute_locally);
+  } else {
+    SignalAudioMuteStateChange(nick, was_muted, is_muted);
+  }
+}
+
+const std::string& GetAudioMuteNickFromItem(const XmlElement* item) {
+  if (item != NULL) {
+    const XmlElement* audio_mute_state =
+        item->FirstNamed(QN_GOOGLE_MUC_AUDIO_MUTE);
+    if (audio_mute_state != NULL) {
+      return audio_mute_state->Attr(QN_NICK);
+    }
+  }
+  return STR_EMPTY;
+}
+
+void HangoutPubSubClient::OnAudioMutePublishResult(
+    const std::string& task_id, const XmlElement* item) {
+  const std::string& mutee_nick = GetAudioMuteNickFromItem(item);
+  if (mutee_nick != nick_) {
+    SignalRemoteMuteResult(task_id, mutee_nick);
+  } else {
+    SignalPublishAudioMuteResult(task_id);
+  }
+}
+
+void HangoutPubSubClient::OnAudioMutePublishError(
+    const std::string& task_id, const XmlElement* item,
+    const XmlElement* stanza) {
+  const std::string& mutee_nick = GetAudioMuteNickFromItem(item);
+  if (mutee_nick != nick_) {
+    SignalRemoteMuteError(task_id, mutee_nick, stanza);
+  } else {
+    SignalPublishAudioMuteError(task_id, stanza);
+  }
+}
+
+void HangoutPubSubClient::OnRecordingStateChange(
+    const PubSubStateChange<bool>& change) {
+  const std::string& nick = change.key;
+  SignalRecordingStateChange(nick, change.old_state, change.new_state);
+}
+
+void HangoutPubSubClient::OnRecordingPublishResult(
+    const std::string& task_id, const XmlElement* item) {
+  SignalPublishRecordingResult(task_id);
+}
+
+void HangoutPubSubClient::OnRecordingPublishError(
+    const std::string& task_id, const XmlElement* item,
+    const XmlElement* stanza) {
+  SignalPublishRecordingError(task_id, stanza);
+}
+
+}  // namespace buzz
diff --git a/talk/xmpp/hangoutpubsubclient.h b/talk/xmpp/hangoutpubsubclient.h
new file mode 100644
index 0000000..e740738
--- /dev/null
+++ b/talk/xmpp/hangoutpubsubclient.h
@@ -0,0 +1,157 @@
+/*
+ * 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_XMPP_HANGOUTPUBSUBCLIENT_H_
+#define TALK_XMPP_HANGOUTPUBSUBCLIENT_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/sigslotrepeater.h"
+#include "talk/xmpp/jid.h"
+#include "talk/xmpp/pubsubclient.h"
+
+// Gives a high-level API for MUC call PubSub needs such as
+// presenter state, recording state, mute state, and remote mute.
+
+namespace buzz {
+
+class Jid;
+class XmlElement;
+class XmppTaskParentInterface;
+
+// Represents a PubSub state change.  Usually, the key is the nick,
+// but not always.  It's a per-state-type thing.
+template <typename C>
+struct PubSubStateChange {
+  std::string key;
+  C old_state;
+  C new_state;
+  std::string publisher_nick;
+};
+
+template <typename C> class PubSubStateClient;
+
+// A client tied to a specific MUC jid and local nick.  Provides ways
+// to get updates and publish state and events.  Must call
+// RequestAll() to start getting updates.
+class HangoutPubSubClient : public sigslot::has_slots<> {
+ public:
+  HangoutPubSubClient(XmppTaskParentInterface* parent,
+                      const Jid& mucjid,
+                      const std::string& nick);
+  ~HangoutPubSubClient();
+  const Jid& mucjid() const { return mucjid_; }
+  const std::string& nick() const { return nick_; }
+
+  // Requests all of the different states and subscribes for updates.
+  // Responses and updates will be signalled via the various signals.
+  void RequestAll();
+  // Signal (nick, was_presenting, is_presenting)
+  sigslot::signal3<const std::string&, bool, bool> SignalPresenterStateChange;
+  // Signal (nick, was_muted, is_muted)
+  sigslot::signal3<const std::string&, bool, bool> SignalAudioMuteStateChange;
+  // Signal (nick, was_recording, is_recording)
+  sigslot::signal3<const std::string&, bool, bool> SignalRecordingStateChange;
+  // Signal (muter_nick, mutee_nick, should_mute_locally)
+  sigslot::signal3<const std::string&,
+                   const std::string&,
+                   bool> SignalRemoteMute;
+
+  // Signal (node, error stanza)
+  sigslot::signal2<const std::string&, const XmlElement*> SignalRequestError;
+
+  // On each of these, provide a task_id_out to get the task_id, which
+  // can be correlated to the error and result signals.
+  void PublishPresenterState(
+      bool presenting, std::string* task_id_out = NULL);
+  void PublishAudioMuteState(
+      bool muted, std::string* task_id_out = NULL);
+  void PublishRecordingState(
+      bool recording, std::string* task_id_out = NULL);
+  void RemoteMute(
+      const std::string& mutee_nick, std::string* task_id_out = NULL);
+
+  // Signal task_id
+  sigslot::signal1<const std::string&> SignalPublishAudioMuteResult;
+  sigslot::signal1<const std::string&> SignalPublishPresenterResult;
+  sigslot::signal1<const std::string&> SignalPublishRecordingResult;
+  // Signal (task_id, mutee_nick)
+  sigslot::signal2<const std::string&,
+                   const std::string&> SignalRemoteMuteResult;
+
+  // Signal (task_id, error stanza)
+  sigslot::signal2<const std::string&,
+                   const XmlElement*> SignalPublishAudioMuteError;
+  sigslot::signal2<const std::string&,
+                   const XmlElement*> SignalPublishPresenterError;
+  sigslot::signal2<const std::string&,
+                   const XmlElement*> SignalPublishRecordingError;
+  // Signal (task_id, mutee_nick, error stanza)
+  sigslot::signal3<const std::string&,
+                   const std::string&,
+                   const XmlElement*> SignalRemoteMuteError;
+
+ private:
+  void OnPresenterRequestError(PubSubClient* client,
+                               const XmlElement* stanza);
+  void OnMediaRequestError(PubSubClient* client,
+                           const XmlElement* stanza);
+
+  void OnPresenterStateChange(const PubSubStateChange<bool>& change);
+  void OnPresenterPublishResult(const std::string& task_id,
+                               const XmlElement* item);
+  void OnPresenterPublishError(const std::string& task_id,
+                               const XmlElement* item,
+                               const XmlElement* stanza);
+  void OnAudioMuteStateChange(const PubSubStateChange<bool>& change);
+  void OnAudioMutePublishResult(const std::string& task_id,
+                               const XmlElement* item);
+  void OnAudioMutePublishError(const std::string& task_id,
+                               const XmlElement* item,
+                               const XmlElement* stanza);
+  void OnRecordingStateChange(const PubSubStateChange<bool>& change);
+  void OnRecordingPublishResult(const std::string& task_id,
+                               const XmlElement* item);
+  void OnRecordingPublishError(const std::string& task_id,
+                               const XmlElement* item,
+                               const XmlElement* stanza);
+  Jid mucjid_;
+  std::string nick_;
+  talk_base::scoped_ptr<PubSubClient> media_client_;
+  talk_base::scoped_ptr<PubSubClient> presenter_client_;
+  talk_base::scoped_ptr<PubSubStateClient<bool> > presenter_state_client_;
+  talk_base::scoped_ptr<PubSubStateClient<bool> > audio_mute_state_client_;
+  talk_base::scoped_ptr<PubSubStateClient<bool> > recording_state_client_;
+};
+
+}  // namespace buzz
+
+#endif  // TALK_XMPP_HANGOUTPUBSUBCLIENT_H_
diff --git a/talk/xmpp/iqtask.h b/talk/xmpp/iqtask.h
index 589616b..2228e6f 100644
--- a/talk/xmpp/iqtask.h
+++ b/talk/xmpp/iqtask.h
@@ -35,27 +35,29 @@
 
 namespace buzz {
 
-class IqTask : public buzz::XmppTask {
+class IqTask : public XmppTask {
  public:
   IqTask(XmppTaskParentInterface* parent,
-         const std::string& verb, const buzz::Jid& to,
-         buzz::XmlElement* el);
+         const std::string& verb, const Jid& to,
+         XmlElement* el);
   virtual ~IqTask() {}
 
+  const XmlElement* stanza() const { return stanza_.get(); }
+
   sigslot::signal2<IqTask*,
                    const XmlElement*> SignalError;
 
  protected:
-  virtual void HandleResult(const buzz::XmlElement* element) = 0;
+  virtual void HandleResult(const XmlElement* element) = 0;
 
  private:
   virtual int ProcessStart();
-  virtual bool HandleStanza(const buzz::XmlElement* stanza);
+  virtual bool HandleStanza(const XmlElement* stanza);
   virtual int ProcessResponse();
   virtual int OnTimeout();
 
-  buzz::Jid to_;
-  talk_base::scoped_ptr<buzz::XmlElement> stanza_;
+  Jid to_;
+  talk_base::scoped_ptr<XmlElement> stanza_;
 };
 
 }  // namespace buzz
diff --git a/talk/xmpp/pubsubclient.cc b/talk/xmpp/pubsubclient.cc
new file mode 100644
index 0000000..8d6d4c4
--- /dev/null
+++ b/talk/xmpp/pubsubclient.cc
@@ -0,0 +1,137 @@
+/*
+ * 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/xmpp/pubsubclient.h"
+
+#include <string>
+#include <vector>
+
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/jid.h"
+#include "talk/xmpp/pubsubtasks.h"
+
+namespace buzz {
+
+void PubSubClient::RequestItems() {
+  PubSubRequestTask* request_task =
+      new PubSubRequestTask(parent_, pubsubjid_, node_);
+  request_task->SignalResult.connect(this, &PubSubClient::OnRequestResult);
+  request_task->SignalError.connect(this, &PubSubClient::OnRequestError);
+
+  PubSubReceiveTask* receive_task =
+      new PubSubReceiveTask(parent_, pubsubjid_, node_);
+  receive_task->SignalUpdate.connect(this, &PubSubClient::OnReceiveUpdate);
+
+  receive_task->Start();
+  request_task->Start();
+}
+
+void PubSubClient::PublishItem(
+    const std::string& itemid, XmlElement* payload, std::string* task_id_out) {
+  std::vector<XmlElement*> children;
+  children.push_back(payload);
+  PublishItem(itemid, children, task_id_out);
+}
+
+void PubSubClient::PublishItem(
+    const std::string& itemid, const std::vector<XmlElement*>& children,
+    std::string* task_id_out) {
+  PubSubPublishTask* publish_task =
+      new PubSubPublishTask(parent_, pubsubjid_, node_, itemid, children);
+  publish_task->SignalError.connect(this, &PubSubClient::OnPublishError);
+  publish_task->SignalResult.connect(this, &PubSubClient::OnPublishResult);
+  publish_task->Start();
+  if (task_id_out) {
+    *task_id_out = publish_task->task_id();
+  }
+}
+
+void PubSubClient::RetractItem(
+    const std::string& itemid, std::string* task_id_out) {
+  PubSubRetractTask* retract_task =
+      new PubSubRetractTask(parent_, pubsubjid_, node_, itemid);
+  retract_task->SignalError.connect(this, &PubSubClient::OnRetractError);
+  retract_task->SignalResult.connect(this, &PubSubClient::OnRetractResult);
+  retract_task->Start();
+  if (task_id_out) {
+    *task_id_out = retract_task->task_id();
+  }
+}
+
+void PubSubClient::OnRequestResult(PubSubRequestTask* task,
+                                   const std::vector<PubSubItem>& items) {
+  SignalItems(this, items);
+}
+
+void PubSubClient::OnRequestError(IqTask* task,
+                                  const XmlElement* stanza) {
+  SignalRequestError(this, stanza);
+}
+
+void PubSubClient::OnReceiveUpdate(PubSubReceiveTask* task,
+                                   const std::vector<PubSubItem>& items) {
+  SignalItems(this, items);
+}
+
+const XmlElement* GetItemFromStanza(const XmlElement* stanza) {
+  if (stanza != NULL) {
+    const XmlElement* pubsub = stanza->FirstNamed(QN_PUBSUB);
+    if (pubsub != NULL) {
+      const XmlElement* publish = pubsub->FirstNamed(QN_PUBSUB_PUBLISH);
+      if (publish != NULL) {
+        return publish->FirstNamed(QN_PUBSUB_ITEM);
+      }
+    }
+  }
+  return NULL;
+}
+
+void PubSubClient::OnPublishResult(PubSubPublishTask* task) {
+  const XmlElement* item = GetItemFromStanza(task->stanza());
+  SignalPublishResult(this, task->task_id(), item);
+}
+
+void PubSubClient::OnPublishError(IqTask* task,
+                                  const XmlElement* error_stanza) {
+  PubSubPublishTask* publish_task =
+      static_cast<PubSubPublishTask*>(task);
+  const XmlElement* item = GetItemFromStanza(publish_task->stanza());
+  SignalPublishError(this, publish_task->task_id(), item, error_stanza);
+}
+
+void PubSubClient::OnRetractResult(PubSubRetractTask* task) {
+  SignalRetractResult(this, task->task_id());
+}
+
+void PubSubClient::OnRetractError(IqTask* task,
+                                  const XmlElement* stanza) {
+  PubSubRetractTask* retract_task =
+      static_cast<PubSubRetractTask*>(task);
+  SignalRetractError(this, retract_task->task_id(), stanza);
+}
+
+}  // namespace buzz
diff --git a/talk/xmpp/pubsubclient.h b/talk/xmpp/pubsubclient.h
new file mode 100644
index 0000000..099765a
--- /dev/null
+++ b/talk/xmpp/pubsubclient.h
@@ -0,0 +1,125 @@
+/*
+ * 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_XMPP_PUBSUBCLIENT_H_
+#define TALK_XMPP_PUBSUBCLIENT_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/sigslot.h"
+#include "talk/base/sigslotrepeater.h"
+#include "talk/base/task.h"
+#include "talk/xmpp/jid.h"
+#include "talk/xmpp/pubsubtasks.h"
+
+// Easy to use clients built on top of the tasks for XEP-0060
+// (http://xmpp.org/extensions/xep-0060.html).
+
+namespace buzz {
+
+class Jid;
+class XmlElement;
+class XmppTaskParentInterface;
+
+// An easy-to-use pubsub client that handles the three tasks of
+// getting, publishing, and listening for updates.  Tied to a specific
+// pubsub jid and node.  All you have to do is RequestItems, listen
+// for SignalItems and PublishItems.
+class PubSubClient : public sigslot::has_slots<> {
+ public:
+  PubSubClient(XmppTaskParentInterface* parent,
+               const Jid& pubsubjid,
+               const std::string& node)
+    : parent_(parent),
+      pubsubjid_(pubsubjid),
+      node_(node) {}
+
+  const std::string& node() const { return node_; }
+
+  // Requests the <pubsub><items>, which will be returned via
+  // SignalItems, or SignalRequestError if there is a failure.  Should
+  // auto-subscribe.
+  void RequestItems();
+  // Fired when either <pubsub><items> are returned or when
+  // <event><items> are received.
+  sigslot::signal2<PubSubClient*,
+                   const std::vector<PubSubItem>&> SignalItems;
+  // Signal (this, error stanza)
+  sigslot::signal2<PubSubClient*,
+                   const XmlElement*> SignalRequestError;
+  // Signal (this, task_id, item, error stanza)
+  sigslot::signal4<PubSubClient*,
+                   const std::string&,
+                   const XmlElement*,
+                   const XmlElement*> SignalPublishError;
+  // Signal (this, task_id, item)
+  sigslot::signal3<PubSubClient*,
+                   const std::string&,
+                   const XmlElement*> SignalPublishResult;
+  // Signal (this, task_id, error stanza)
+  sigslot::signal3<PubSubClient*,
+                   const std::string&,
+                   const XmlElement*> SignalRetractError;
+  // Signal (this, task_id)
+  sigslot::signal2<PubSubClient*,
+                   const std::string&> SignalRetractResult;
+
+  // Publish an item.  Takes ownership of payload.
+  void PublishItem(const std::string& itemid,
+                   XmlElement* payload,
+                   std::string* task_id_out);
+  // Publish an item.  Takes ownership of children.
+  void PublishItem(const std::string& itemid,
+                   const std::vector<XmlElement*>& children,
+                   std::string* task_id_out);
+  // Retract (delete) an item.
+  void RetractItem(const std::string& itemid,
+                   std::string* task_id_out);
+
+ private:
+  void OnRequestError(IqTask* task,
+                      const XmlElement* stanza);
+  void OnRequestResult(PubSubRequestTask* task,
+                       const std::vector<PubSubItem>& items);
+  void OnReceiveUpdate(PubSubReceiveTask* task,
+                       const std::vector<PubSubItem>& items);
+  void OnPublishResult(PubSubPublishTask* task);
+  void OnPublishError(IqTask* task,
+                      const XmlElement* stanza);
+  void OnRetractResult(PubSubRetractTask* task);
+  void OnRetractError(IqTask* task,
+                      const XmlElement* stanza);
+
+  XmppTaskParentInterface* parent_;
+  Jid pubsubjid_;
+  std::string node_;
+};
+
+}  // namespace buzz
+
+#endif  // TALK_XMPP_PUBSUBCLIENT_H_
diff --git a/talk/xmpp/pubsubtasks.cc b/talk/xmpp/pubsubtasks.cc
new file mode 100644
index 0000000..bbefbe5
--- /dev/null
+++ b/talk/xmpp/pubsubtasks.cc
@@ -0,0 +1,214 @@
+/*
+ * 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/xmpp/pubsubtasks.h"
+
+#include <string>
+#include <vector>
+
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/receivetask.h"
+
+// An implementation of the tasks for XEP-0060
+// (http://xmpp.org/extensions/xep-0060.html).
+
+namespace buzz {
+
+namespace {
+
+bool IsPubSubEventItemsElem(const XmlElement* stanza,
+                            const std::string& expected_node) {
+  if (stanza->Name() != QN_MESSAGE) {
+    return false;
+  }
+
+  const XmlElement* event_elem = stanza->FirstNamed(QN_PUBSUB_EVENT);
+  if (event_elem == NULL) {
+    return false;
+  }
+
+  const XmlElement* items_elem = event_elem->FirstNamed(QN_PUBSUB_EVENT_ITEMS);
+  if (items_elem == NULL) {
+    return false;
+  }
+
+  const std::string& actual_node = items_elem->Attr(QN_NODE);
+  return (actual_node == expected_node);
+}
+
+
+// Creates <pubsub node="node"><items></pubsub>
+XmlElement* CreatePubSubItemsElem(const std::string& node) {
+  XmlElement* items_elem = new XmlElement(QN_PUBSUB_ITEMS, false);
+  items_elem->AddAttr(QN_NODE, node);
+  XmlElement* pubsub_elem = new XmlElement(QN_PUBSUB, false);
+  pubsub_elem->AddElement(items_elem);
+  return pubsub_elem;
+}
+
+// Creates <pubsub node="node"><publish><item id="itemid">payload</item>...
+// Takes ownership of payload.
+XmlElement* CreatePubSubPublishItemElem(
+    const std::string& node,
+    const std::string& itemid,
+    const std::vector<XmlElement*>& children) {
+  XmlElement* pubsub_elem = new XmlElement(QN_PUBSUB, true);
+  XmlElement* publish_elem = new XmlElement(QN_PUBSUB_PUBLISH, false);
+  publish_elem->AddAttr(QN_NODE, node);
+  XmlElement* item_elem = new XmlElement(QN_PUBSUB_ITEM, false);
+  item_elem->AddAttr(QN_ID, itemid);
+  for (std::vector<XmlElement*>::const_iterator child = children.begin();
+       child != children.end(); ++child) {
+    item_elem->AddElement(*child);
+  }
+  publish_elem->AddElement(item_elem);
+  pubsub_elem->AddElement(publish_elem);
+  return pubsub_elem;
+}
+
+// Creates <pubsub node="node"><publish><item id="itemid">payload</item>...
+// Takes ownership of payload.
+XmlElement* CreatePubSubRetractItemElem(const std::string& node,
+                                        const std::string& itemid) {
+  XmlElement* pubsub_elem = new XmlElement(QN_PUBSUB, true);
+  XmlElement* retract_elem = new XmlElement(QN_PUBSUB_RETRACT, false);
+  retract_elem->AddAttr(QN_NODE, node);
+  retract_elem->AddAttr(QN_NOTIFY, "true");
+  XmlElement* item_elem = new XmlElement(QN_PUBSUB_ITEM, false);
+  item_elem->AddAttr(QN_ID, itemid);
+  retract_elem->AddElement(item_elem);
+  pubsub_elem->AddElement(retract_elem);
+  return pubsub_elem;
+}
+
+void ParseItem(const XmlElement* item_elem,
+               std::vector<PubSubItem>* items) {
+  PubSubItem item;
+  item.itemid = item_elem->Attr(QN_ID);
+  item.elem = item_elem;
+  items->push_back(item);
+}
+
+// Right now, <retract>s are treated the same as items with empty
+// payloads.  We may want to change it in the future, but right now
+// it's sufficient for our needs.
+void ParseRetract(const XmlElement* retract_elem,
+                  std::vector<PubSubItem>* items) {
+  ParseItem(retract_elem, items);
+}
+
+void ParseEventItemsElem(const XmlElement* stanza,
+                         std::vector<PubSubItem>* items) {
+  const XmlElement* event_elem = stanza->FirstNamed(QN_PUBSUB_EVENT);
+  if (event_elem != NULL) {
+    const XmlElement* items_elem =
+        event_elem->FirstNamed(QN_PUBSUB_EVENT_ITEMS);
+    if (items_elem != NULL) {
+      for (const XmlElement* item_elem =
+             items_elem->FirstNamed(QN_PUBSUB_EVENT_ITEM);
+           item_elem != NULL;
+           item_elem = item_elem->NextNamed(QN_PUBSUB_EVENT_ITEM)) {
+        ParseItem(item_elem, items);
+      }
+      for (const XmlElement* retract_elem =
+             items_elem->FirstNamed(QN_PUBSUB_EVENT_RETRACT);
+           retract_elem != NULL;
+           retract_elem = retract_elem->NextNamed(QN_PUBSUB_EVENT_RETRACT)) {
+        ParseRetract(retract_elem, items);
+      }
+    }
+  }
+}
+
+void ParsePubSubItemsElem(const XmlElement* stanza,
+                          std::vector<PubSubItem>* items) {
+  const XmlElement* pubsub_elem = stanza->FirstNamed(QN_PUBSUB);
+  if (pubsub_elem != NULL) {
+    const XmlElement* items_elem = pubsub_elem->FirstNamed(QN_PUBSUB_ITEMS);
+    if (items_elem != NULL) {
+      for (const XmlElement* item_elem = items_elem->FirstNamed(QN_PUBSUB_ITEM);
+           item_elem != NULL;
+           item_elem = item_elem->NextNamed(QN_PUBSUB_ITEM)) {
+        ParseItem(item_elem, items);
+      }
+    }
+  }
+}
+
+}  // namespace
+
+PubSubRequestTask::PubSubRequestTask(XmppTaskParentInterface* parent,
+                                     const Jid& pubsubjid,
+                                     const std::string& node)
+    : IqTask(parent, STR_GET, pubsubjid, CreatePubSubItemsElem(node)) {
+}
+
+void PubSubRequestTask::HandleResult(const XmlElement* stanza) {
+  std::vector<PubSubItem> items;
+  ParsePubSubItemsElem(stanza, &items);
+  SignalResult(this, items);
+}
+
+bool PubSubReceiveTask::WantsStanza(const XmlElement* stanza) {
+  return MatchStanzaFrom(stanza, pubsubjid_) &&
+      IsPubSubEventItemsElem(stanza, node_);
+}
+
+void PubSubReceiveTask::ReceiveStanza(const XmlElement* stanza) {
+  std::vector<PubSubItem> items;
+  ParseEventItemsElem(stanza, &items);
+  SignalUpdate(this, items);
+}
+
+PubSubPublishTask::PubSubPublishTask(XmppTaskParentInterface* parent,
+                                     const Jid& pubsubjid,
+                                     const std::string& node,
+                                     const std::string& itemid,
+                                     const std::vector<XmlElement*>& children)
+    : IqTask(parent, STR_SET, pubsubjid,
+             CreatePubSubPublishItemElem(node, itemid, children)),
+      itemid_(itemid) {
+}
+
+void PubSubPublishTask::HandleResult(const XmlElement* stanza) {
+  SignalResult(this);
+}
+
+PubSubRetractTask::PubSubRetractTask(XmppTaskParentInterface* parent,
+                                     const Jid& pubsubjid,
+                                     const std::string& node,
+                                     const std::string& itemid)
+    : IqTask(parent, STR_SET, pubsubjid,
+             CreatePubSubRetractItemElem(node, itemid)),
+      itemid_(itemid) {
+}
+
+void PubSubRetractTask::HandleResult(const XmlElement* stanza) {
+  SignalResult(this);
+}
+
+}  // namespace buzz
diff --git a/talk/xmpp/pubsubtasks.h b/talk/xmpp/pubsubtasks.h
new file mode 100644
index 0000000..f0a1581
--- /dev/null
+++ b/talk/xmpp/pubsubtasks.h
@@ -0,0 +1,130 @@
+/*
+ * 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_XMPP_PUBSUBTASKS_H_
+#define TALK_XMPP_PUBSUBTASKS_H_
+
+#include <vector>
+
+#include "talk/base/sigslot.h"
+#include "talk/xmpp/iqtask.h"
+#include "talk/xmpp/receivetask.h"
+
+namespace buzz {
+
+// A PubSub itemid + payload.  Useful for signaling items.
+struct PubSubItem {
+  std::string itemid;
+  // The entire <item>, owned by the stanza handler.  To keep a
+  // reference after handling, make a copy.
+  const XmlElement* elem;
+};
+
+// An IqTask which gets a <pubsub><items> for a particular jid and
+// node, parses the items in the response and signals the items.
+class PubSubRequestTask : public IqTask {
+ public:
+  PubSubRequestTask(XmppTaskParentInterface* parent,
+                    const Jid& pubsubjid,
+                    const std::string& node);
+
+  sigslot::signal2<PubSubRequestTask*,
+                   const std::vector<PubSubItem>&> SignalResult;
+  // SignalError inherited by IqTask.
+ private:
+  virtual void HandleResult(const XmlElement* stanza);
+};
+
+// A ReceiveTask which listens for <event><items> of a particular
+// pubsub JID and node and then signals them items.
+class PubSubReceiveTask : public ReceiveTask {
+ public:
+  PubSubReceiveTask(XmppTaskParentInterface* parent,
+                    const Jid& pubsubjid,
+                    const std::string& node)
+      : ReceiveTask(parent),
+        pubsubjid_(pubsubjid),
+        node_(node) {
+  }
+
+  sigslot::signal2<PubSubReceiveTask*,
+                   const std::vector<PubSubItem>&> SignalUpdate;
+
+ protected:
+  virtual bool WantsStanza(const XmlElement* stanza);
+  virtual void ReceiveStanza(const XmlElement* stanza);
+
+ private:
+  Jid pubsubjid_;
+  std::string node_;
+};
+
+// An IqTask which publishes a <pubsub><publish><item> to a particular
+// pubsub jid and node.
+class PubSubPublishTask : public IqTask {
+ public:
+  // Takes ownership of children
+  PubSubPublishTask(XmppTaskParentInterface* parent,
+                    const Jid& pubsubjid,
+                    const std::string& node,
+                    const std::string& itemid,
+                    const std::vector<XmlElement*>& children);
+
+  const std::string& itemid() const { return itemid_; }
+
+  sigslot::signal1<PubSubPublishTask*> SignalResult;
+
+ private:
+  // SignalError inherited by IqTask.
+  virtual void HandleResult(const XmlElement* stanza);
+
+  std::string itemid_;
+};
+
+// An IqTask which publishes a <pubsub><publish><retract> to a particular
+// pubsub jid and node.
+class PubSubRetractTask : public IqTask {
+ public:
+  PubSubRetractTask(XmppTaskParentInterface* parent,
+                    const Jid& pubsubjid,
+                    const std::string& node,
+                    const std::string& itemid);
+
+  const std::string& itemid() const { return itemid_; }
+
+  sigslot::signal1<PubSubRetractTask*> SignalResult;
+
+ private:
+  // SignalError inherited by IqTask.
+  virtual void HandleResult(const XmlElement* stanza);
+
+  std::string itemid_;
+};
+
+}  // namespace buzz
+
+#endif  // TALK_XMPP_PUBSUBTASKS_H_
diff --git a/talk/xmpp/receivetask.cc b/talk/xmpp/receivetask.cc
new file mode 100644
index 0000000..53fac7e
--- /dev/null
+++ b/talk/xmpp/receivetask.cc
@@ -0,0 +1,51 @@
+/*
+ * 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/xmpp/receivetask.h"
+#include "talk/xmpp/constants.h"
+
+namespace buzz {
+
+bool ReceiveTask::HandleStanza(const XmlElement* stanza) {
+  if (WantsStanza(stanza)) {
+    QueueStanza(stanza);
+    return true;
+  }
+
+  return false;
+}
+
+int ReceiveTask::ProcessStart() {
+  const XmlElement* stanza = NextStanza();
+  if (stanza == NULL)
+    return STATE_BLOCKED;
+
+  ReceiveStanza(stanza);
+  return STATE_START;
+}
+
+}  // namespace buzz
diff --git a/talk/xmpp/receivetask.h b/talk/xmpp/receivetask.h
new file mode 100644
index 0000000..b18e0f0
--- /dev/null
+++ b/talk/xmpp/receivetask.h
@@ -0,0 +1,58 @@
+/*
+ * 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_XMPP_RECEIVETASK_H_
+#define TALK_XMPP_RECEIVETASK_H_
+
+#include "talk/xmpp/xmpptask.h"
+
+namespace buzz {
+
+// A base class for receiving stanzas.  Override WantsStanza to
+// indicate that a stanza should be received and ReceiveStanza to
+// process it.  Once started, ReceiveStanza will be called for all
+// stanzas that return true when passed to WantsStanza. This saves
+// you from having to remember how to setup the queueing and the task
+// states, etc.
+class ReceiveTask : public XmppTask {
+ public:
+  explicit ReceiveTask(XmppTaskParentInterface* parent) :
+      XmppTask(parent, XmppEngine::HL_TYPE) {}
+  virtual int ProcessStart();
+
+ protected:
+  virtual bool HandleStanza(const XmlElement* stanza);
+
+  // Return true if the stanza should be received.
+  virtual bool WantsStanza(const XmlElement* stanza) = 0;
+  // Process the received stanza.
+  virtual void ReceiveStanza(const XmlElement* stanza) = 0;
+};
+
+}  // namespace buzz
+
+#endif  // TALK_XMPP_RECEIVETASK_H_
diff --git a/talk/xmpp/xmppclient.cc b/talk/xmpp/xmppclient.cc
index 578884a..09637c8 100644
--- a/talk/xmpp/xmppclient.cc
+++ b/talk/xmpp/xmppclient.cc
@@ -140,7 +140,7 @@
 }
 
 XmppEngine::State
-XmppClient::GetState() {
+XmppClient::GetState() const {
   if (d_->engine_.get() == NULL)
     return XmppEngine::STATE_NONE;
   return d_->engine_->GetState();
@@ -266,6 +266,7 @@
 XmppClient::Disconnect() {
   if (d_->socket_.get() == NULL)
     return XMPP_RETURN_BADSTATE;
+  Abort();
   d_->engine_->Disconnect();
   d_->socket_.reset(NULL);
   return XMPP_RETURN_OK;
@@ -284,7 +285,7 @@
 }
 
 const Jid &
-XmppClient::jid() {
+XmppClient::jid() const {
   return d_->engine_->FullJid();
 }
 
diff --git a/talk/xmpp/xmppclient.h b/talk/xmpp/xmppclient.h
index b8f854e..ad8b9a2 100644
--- a/talk/xmpp/xmppclient.h
+++ b/talk/xmpp/xmppclient.h
@@ -86,7 +86,6 @@
   XmppReturnStatus Disconnect();
 
   sigslot::signal1<XmppEngine::State> SignalStateChange;
-  XmppEngine::State GetState();
   XmppEngine::Error GetError(int *subcode);
 
   // When there is a <stream:error> stanza, return the stanza
@@ -112,7 +111,8 @@
   virtual XmppClientInterface* GetClient() { return this; }
 
   // As XmppClientInterface
-  virtual const Jid& jid();
+  virtual XmppEngine::State GetState() const;
+  virtual const Jid& jid() const;
   virtual std::string NextId();
   virtual XmppReturnStatus SendStanza(const XmlElement *stanza);
   virtual XmppReturnStatus SendStanzaError(const XmlElement * pelOriginal,
diff --git a/talk/xmpp/xmpptask.h b/talk/xmpp/xmpptask.h
index fd82c79..6a88f98 100644
--- a/talk/xmpp/xmpptask.h
+++ b/talk/xmpp/xmpptask.h
@@ -73,7 +73,8 @@
   XmppClientInterface();
   virtual ~XmppClientInterface();
 
-  virtual const Jid& jid() = 0;
+  virtual XmppEngine::State GetState() const = 0;
+  virtual const Jid& jid() const = 0;
   virtual std::string NextId() = 0;
   virtual XmppReturnStatus SendStanza(const XmlElement* stanza) = 0;
   virtual XmppReturnStatus SendStanzaError(const XmlElement* original_stanza,